Moc.Tests.ps1
|
BeforeAll { Import-Module Pester ipmo .\Moc.psm1 Import-Module $PSScriptRoot\MocTestMocks.psm1 $cluster = "s-cluster" } AfterAll { Remove-Module Moc Remove-Module MocTestMocks } # Describe 'Set-MocConfig' { # BeforeAll { # $global:config = @{} # $global:config[$global:MocModule] = @{} # } # It 'Should be able to create ssh configuration and set-mocconfig without IP' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" # $sshConfig.Name | should -Be "test" # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $false # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -BeNullOrEmpty # (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty # } # It 'Should be able to create ssh configuration and set-mocconfig with IP Address' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1') # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $false # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -BeNullOrEmpty # (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1" # } # It 'Should be able to create ssh configuration and set-mocconfig with IP Addresses' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1', '127.0.0.2') # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $false # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -BeNullOrEmpty # (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1,127.0.0.2" # } # It 'Should be able to create ssh configuration and set-mocconfig with CIDR' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $false # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25" # (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty # } # It 'Should be able to create ssh configuration and set-mocconfig with sshRestriction' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $true # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25" # (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty # } # It 'Should be able to create ssh configuration and set-mocconfig with public & private key' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # Set-Content -value "1234" -path "C:\test\temp.pub" # Set-Content -value "4567" -path "C:\test\temp" # $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands -sshPublicKey "C:\test\temp.pub" -sshPrivateKey "C:\test\temp" # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $true # (Get-MocConfig).sshPublicKey | should -Be "C:\test\temp.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\test\temp" # (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25" # (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty # } # It 'Get-SSHAuthConfigSudoRestrictions with public & private key' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # Set-Content -value "1234" -path "C:\test\temp.pub" # Set-Content -value "4567" -path "C:\test\temp" # $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands -sshPublicKey "C:\test\temp.pub" -sshPrivateKey "C:\test\temp" # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $true # (Get-MocConfig).sshPublicKey | should -Be "C:\test\temp.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\test\temp" # (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25" # (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty # Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1/25"" 1234" # } # It 'Get-SSHAuthConfigSudoRestrictions without public & private key' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -cidr '127.0.0.1/25' -restrictSSHCommands # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $true # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -Be "127.0.0.1/25" # (Get-MocConfig).sshIPAddresses | should -BeNullOrEmpty # $content = get-content -path "C:\AksHci\.ssh\akshci_rsa.pub" # Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1/25"" $content" # } # It 'Get-SSHAuthConfigSudoRestrictions with ip Address' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1') # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $false # (Get-MocConfig).sshPublicKey | should -Be "C:\AksHci\.ssh\akshci_rsa.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\AksHci\.ssh\akshci_rsa" # (Get-MocConfig).sshCidr | should -BeNullOrEmpty # (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1" # $content = get-content -path "C:\AksHci\.ssh\akshci_rsa.pub" # Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1"" $content" # } # It 'Get-SSHAuthConfigSudoRestrictions with ip Addresses' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # $sshConfig = New-SSHConfiguration -name "test" -ipaddresses @('127.0.0.1','127.0.0.2') -sshPublicKey "C:\test\temp.pub" -sshPrivateKey "C:\test\temp" # Set-Content -value "1234" -path "C:\test\temp.pub" # Set-Content -value "4567" -path "C:\test\temp" # $vnet = [VirtualNetwork]::new("defaultswitch", "Default Switch" , "", "", $(), "", 0, "", "", "", "") # Set-MocConfig -ssh $sshConfig -vnet $vnet -skipHostLimitChecks -skipRemotingChecks -skipValidationCheck # (Get-MocConfig).sshConfigName | should -Be "test" # (Get-MocConfig).sshRestrictCommands | should -Be $false # (Get-MocConfig).sshPublicKey | should -Be "C:\test\temp.pub" # (Get-MocConfig).sshPrivateKey | should -Be "C:\test\temp" # (Get-MocConfig).sshCidr | should -BeNullOrEmpty # (Get-MocConfig).sshIPAddresses | should -Be "127.0.0.1,127.0.0.2" # Get-SSHAuthConfigSudoRestrictions | should -Be "from=""127.0.0.1,127.0.0.2"" 1234" # } # It 'Set-MocConfigValue with invalid values' { # Save-ConfigurationDirectory -moduleName $global:AksHciModule -workingDir "C:\test" # { Set-MocConfigValue -name installState -value "invalid" } | Should -Throw # Set-MocConfigValue -name installState -value [InstallState]::InstallFailed # } # } Describe "Set-DirectoryPermission" { It "Should not take more than -Timeout parameter" { $modulename = "Moc" $workingDir = Join-Path -Path (Get-Location) -ChildPath "test" $installationDirectory = Join-Path -Path $workingDir -ChildPath "1.0.23.10605" $yamlDirectory = Join-Path -Path $installationDirectory -ChildPath "yaml" $imageDir = Join-Path -Path $workingDir -ChildPath "images" $cloudConfigLocation = Join-Path -Path $workingDir -ChildPath "cloud" Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue New-Item -Path $workingDir -ItemType Directory -Force | Out-Null New-Item -Path $installationDirectory -ItemType Directory -Force | Out-Null New-Item -Path $yamlDirectory -ItemType Directory -Force | Out-Null New-Item -Path $imageDir -ItemType Directory -Force | Out-Null New-Item -Path $cloudConfigLocation -ItemType Directory -Force | Out-Null $global:config[$modulename]["workingDir"] = $workingDir $global:config[$modulename]["installationPackageDir"] = $installationDirectory $global:config[$modulename]["imageDir"] = $imageDir $global:config[$modulename]["cloudConfigLocation"] = $cloudConfigLocation # simulate Set-SecurePermissionFolder takes way longer Mock -ModuleName "Common" Start-Job { $scriptBlock = { Start-Sleep -Seconds 10 return "Job completed" } $randStrings = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 4 | % {[char]$_}) $jobname = $randStrings + "Permission" $job = Start-Job -name $jobname -ScriptBlock $scriptBlock return $job } # set start time $startTime = Get-Date Set-DirectoryPermission -timeoutSeconds 3 $endTime = Get-Date # Set-DirectoryPermission should not take more than 3 seconds for each Set-SecurePermissionFolder job ($endTime - $startTime).TotalSeconds | Should -BeLessThan 20 # clean up Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue $global:config[$modulename] = $null } } Describe "Test-InstallStateEligibility" -Tag "test" { <# InstallState Set-MocConfig Install-Moc NotInstalled Proceed Proceed Installing (has no active process) Proceed Proceed Installing (has active process) Exit Exit InstallFailed Proceed Proceed Updating Error Error UpdateFailed Error Error Uninstalling Error Error UninstallFailed Error Error Installed Exit Exit #> BeforeAll { $NoThrow = "NoThrow" $ShouldThrow = "ShouldThrow" $ShouldExit = "ShouldExit" function Assert-InstallStateEligibility { param( [string]$Activity, [ValidateSet('NoThrow', 'ShouldThrow', 'ShouldExit', IgnoreCase = $false)] [string]$ExpectedErrorType ) switch ($ExpectedErrorType) { 'NoThrow' { { Test-InstallStateEligibility -activity $Activity } | Should -Not -Throw } default { try { Test-InstallStateEligibility -activity $Activity throw "Expected exception of type '$ExpectedErrorType' but none was thrown." } catch { $_.Exception.ErrorType | Should -Be $ExpectedErrorType } } } } } # set-mocconfig is not allowed but install-moc is allowed It "when installstate is NotInstalled" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::NotInstalled } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::NotInstalled } Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $NoThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $NoThrow } It "when installstate is InstallFailed" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::InstallFailed } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::InstallFailed } Mock -ModuleName "Moc" -CommandName "Uninstall-Moc" {} Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $NoThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $NoThrow Should -Invoke -CommandName Uninstall-Moc -ModuleName "Moc" -Times 1 } It "when installstate is UninstallFailed" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::UninstallFailed } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::UninstallFailed } Mock -ModuleName "Moc" -CommandName "Uninstall-Moc" {} Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow } # Both set-mocconfig and install-moc are not allowed It "when installstate is Updating" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Updating } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Updating } Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow } It "when installstate is UpdateFailed" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::UpdateFailed } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::UpdateFailed } Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow } It "when installstate is Uninstalling" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Uninstalling } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Uninstalling } Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldThrow } # Both set-mocconfig and install-moc are allowed It "when installstate is Installed" { Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Installed } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Installed } Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldExit Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldExit } # Both set-mocconfig and install-moc are allowed when there's no active process It "when installstate is Installing but there's no active process" { # there's no running process on the host Mock -ModuleName "Moc" -CommandName "Get-InvokedPSCommands" {} # however, installstate is Installing because its process was killed Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Installing } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Updating } Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $NoThrow Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $NoThrow } # Both set-mocconfig and install-moc are not allowed when there's active process It "when installstate is Installing and there's active processes of it" { $installMoc = "Install-Moc" $PIDRunningInBackground = 1234 $hostname = "REDMOND-DESKTOP" # Install-Moc was recorded in $mocconfig.commands Mock -ModuleName "Common" -CommandName "Get-InvokedPSCommands" { return @([PSCustomObject]@{ cmdletName = $installMoc processId = $PIDRunningInBackground computerName = $hostname startTime = Get-Date }) } # the process is still running Mock -ModuleName "Common" -CommandName "Get-Process" { return @( [PSCustomObject]@{ ProcessName = "powershell" Id = $PIDRunningInBackground } ) } Mock -ModuleName "Moc" -CommandName "Get-InstallState" { return [InstallState]::Installing } Mock -ModuleName "Common" -CommandName "Get-InstallState" { return [InstallState]::Installing } Mock -ModuleName "Moc" -CommandName "Uninitialize-MocEnvironment" {} Assert-InstallStateEligibility -Activity "Set-MocConfig" -ExpectedErrorType $ShouldExit Assert-InstallStateEligibility -Activity "Install-Moc" -ExpectedErrorType $ShouldExit Should -Invoke -CommandName Uninitialize-MocEnvironment -ModuleName "Moc" -Times 2 } } Describe "Remove-MocDirectories" { It "Should remove all directories with expected names" { $workingDir = Join-Path -Path (Get-Location) -ChildPath "test" $cloudConfigLocation = Join-Path -Path $workingDir -ChildPath "wssdcloudagent" $versionDir = Join-Path -Path $cloudConfigLocation -ChildPath "v0.26.4" $logDir = Join-Path -Path $cloudConfigLocation -ChildPath "log" $certificateDir = Join-Path -Path $versionDir -ChildPath "certificate" $clusterDir = Join-Path -Path $versionDir -ChildPath "cluster" Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue New-Item -Path $cloudConfigLocation -ItemType Directory -Force | Out-Null New-Item -Path $versionDir -ItemType Directory -Force | Out-Null New-Item -Path $logDir -ItemType Directory -Force | Out-Null New-Item -Path $certificateDir -ItemType Directory -Force | Out-Null New-Item -Path $clusterDir -ItemType Directory -Force | Out-Null Mock -ModuleName "Moc" Get-MocConfig { @{ "cloudConfigLocation" = $cloudConfigLocation } } Remove-MocDirectories $dirs = @( $certificateDir, $clusterDir ) foreach ($dir in $dirs) { Test-Path -Path $dir | Should -BeFalse } Remove-Item -Path $workingdir -Recurse -Force -ErrorAction SilentlyContinue } } Describe "Test-FailoverClusterNodeConnectivity" { It "Should fail if a node is not pingable" { Mock -ModuleName "Moc" Get-FailoverCluster { "cluster" } Mock -ModuleName "Moc" Invoke-Command { @{ "PingSucceeded" = $false } } Mock -ModuleName "Moc" Get-ClusterNode { @( @{ "name" = "abc-d120301" }, @{ "name" = "abc-d120302" } ) } $result = Test-FailoverClusterNodeConnectivity $result.TestResult | Should -Be "Failed" } It "Should fail if invoke-command can't be run on a node" { Mock -ModuleName "Moc" Get-FailoverCluster { "cluster" } Mock -ModuleName "Moc" Invoke-Command { throw "Mock exception" } Mock -ModuleName "Moc" Get-ClusterNode { @( @{ "name" = "abc-d120301" }, @{ "name" = "abc-d120302" } ) } $result = Test-FailoverClusterNodeConnectivity $result.TestResult | Should -Be "Failed" #should have the exception $result.Details | Should -Be "Mock exception" } It "Should succeed if all nodes are pingable" { Mock -ModuleName "Moc" Get-FailoverCluster { "cluster" } Mock -ModuleName "Moc" Invoke-Command { @{ "TcpTestSucceeded" = $true } } Mock -ModuleName "Moc" Get-ClusterNode { @( @{ "name" = "abc-d120301" }, @{ "name" = "abc-d120302" } ) } $result = Test-FailoverClusterNodeConnectivity $result.TestResult | Should -Be "Success" } It "Should skip if not a failover cluster" { Mock -ModuleName "Moc" Get-FailoverCluster { $null } $result = Test-FailoverClusterNodeConnectivity $result.TestResult | Should -Be "Skipped" } } Describe "Verify Test-ProxyEnvironmentVariableOnNode" { It "Should fail in case of throw from Invoke-Command [ERROR][WARNING]" { Mock -ModuleName "Moc" Invoke-Command { throw "Mock exception" } foreach ($proxyEnvVar in @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")){ $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120302" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar $result.Severity | Should -Be "WARNING" $result.Status | Should -Be "ERROR" $result.TargetResourceName | Should -Be "abc-d120302" $result.Description | Should -Be "Unable to check $proxyEnvVar environment variable due to exception: Mock exception" $result.Remediation | Should -Be "Please ensure $proxyEnvVar environment variable value is correct on the node. See https://learn.microsoft.com/en-us/azure-stack/hci/manage/configure-proxy-settings-23h2#environment-variables-proxy-bypass-list-string-considerations for full details" } } It "Should succeed in case of valid proxy string [SUCCESS][INFORMATIONAL]" { Mock -ModuleName "Moc" Invoke-Command { "localhost,127.0.0.1,s-cluster" } foreach ($proxyEnvVar in @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")){ $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120303" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar $result.Severity | Should -Be "INFORMATIONAL" $result.Status | Should -Be "SUCCESS" $result.TargetResourceName | Should -Be "abc-d120303" } } It "Should succeed in case of null proxy env var [SUCCESS][INFORMATIONAL]" { Mock -ModuleName "Moc" Invoke-Command { $null } foreach ($proxyEnvVar in @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY")){ $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120303" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar $result.Severity | Should -Be "INFORMATIONAL" $result.Status | Should -Be "SUCCESS" $result.TargetResourceName | Should -Be "abc-d120303" $result.Description | Should -Be "$proxyEnvVar environment variable is null on node, skipping checks" } } It "Should fail in case of invalid string contents [FAILURE][CRITICAL]" { Mock -ModuleName "Moc" Invoke-Command { "localhost,127.0.0.1;s-cluster" } -ParameterFilter { $ComputerName -eq "abc-d120301"} Mock -ModuleName "Moc" Invoke-Command { "localhost,127.0.0.1;s-cluster,*" } -ParameterFilter { $ComputerName -eq "abc-d120302"} Mock -ModuleName "Moc" Invoke-Command { "localhost,127.0.0.1,s-cluster,*" } -ParameterFilter { $ComputerName -eq "abc-d120303"} $fixProxyRemediation = "Please correct {0} environment variable value. See https://learn.microsoft.com/en-us/azure-stack/hci/manage/configure-proxy-settings-23h2#environment-variables-proxy-bypass-list-string-considerations for full details" $proxyEnvironmentVariables = @("HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY") foreach ($proxyEnvVar in $proxyEnvironmentVariables){ $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120301" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar $result.Severity | Should -Be "CRITICAL" $result.Status | Should -Be "FAILURE" $result.TargetResourceName | Should -Be "abc-d120301" $result.Description | Should -Be "$proxyEnvVar environment variable contains invalid wildcard character ;" $result.Remediation | Should -Be $($fixProxyRemediation -f $proxyEnvVar) } foreach ($proxyEnvVar in $proxyEnvironmentVariables){ $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120302" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar $result.Severity | Should -Be "CRITICAL" $result.Status | Should -Be "FAILURE" $result.TargetResourceName | Should -Be "abc-d120302" $result.Description | Should -Be "$proxyEnvVar environment variable contains invalid wildcard characters * and ;" $result.Remediation | Should -Be $($fixProxyRemediation -f $proxyEnvVar) } foreach ($proxyEnvVar in $proxyEnvironmentVariables){ $result = Test-ProxyEnvironmentVariableOnNode -NodeName "abc-d120303" -ClusterName $cluster -ProxyEnvironmentVariable $proxyEnvVar $result.Severity | Should -Be "CRITICAL" $result.Status | Should -Be "FAILURE" $result.TargetResourceName | Should -Be "abc-d120303" $result.Description | Should -Be "$proxyEnvVar environment variable contains invalid wildcard character *" $result.Remediation | Should -Be $($fixProxyRemediation -f $proxyEnvVar) } } } Describe "Verify Test-ProxyEnvironmentVariables" { It "Should succeed if Test-ProxyEnvironmentVariableOnNode succeeds on all nodes" { Mock -ModuleName "Moc" Get-ClusterNode { @( @{ "Name" = "abc-d120302" }, @{ "name" = "abc-d120303" } ) } Mock -ModuleName "Moc" Test-ProxyEnvironmentVariableOnNode { @{ "Status" = "SUCCESS" "TargetResourceName" = "abc-d120302" } } -ParameterFilter { $NodeName -eq "abc-d120302"} Mock -ModuleName "Moc" Test-ProxyEnvironmentVariableOnNode { @{ "Status" = "SUCCESS" "TargetResourceName" = "abc-d120303" } } -ParameterFilter { $NodeName -eq "abc-d120303"} $result = Test-ProxyEnvironmentVariables -Cluster $cluster $result.count | Should -Be 6 for ($i = 0; $i -lt 6; $i++) { $result[$i].Status | Should -Be "SUCCESS" if ($i -lt 3){ $result[$i].TargetResourceName | Should -Be "abc-d120302" } else { $result[$i].TargetResourceName | Should -Be "abc-d120303" } } } } Describe "Verify Test-Precheck" { It "Should throw exception if atleast one precheck result does not succeed and is critical" { $precheckResults = @( @{ "Title" = "HTTPS_PROXY environment variable validity" "Description" = "HTTPS_PROXY environment variable contains invalid wildcard characters * and ;" "TargetResourceID" = "Cluster/" + $cluster + "/Node/abc-d120301" "Remediation" = "Please correct HTTPS_PROXY environment variable value." "Severity" = "CRITICAL" "Status" = "FAILURE" } ) $expectedExceptionMessage = "Moc {0} Prechecks failed, unable to proceed with {0}. Please see the recommended actions for each failure. Precheck failures: " ` + "`nFailed {0} precheck: $($precheckResults[0].Title) failed due to $($precheckResults[0].Description) on resource $($precheckResults[0].TargetResourceID), " ` + "recommendation is: $($precheckResults[0].Remediation)" $expectedInstallExceptionMessage = $expectedExceptionMessage -f "Install" $expectedUpgradeExceptionMessage = $expectedExceptionMessage -f "Upgrade" { Test-Precheck -Results $precheckResults -Operation "Install" } | Should -Throw $expectedInstallExceptionMessage { Test-Precheck -Results $precheckResults -Operation "Upgrade" } | Should -Throw $expectedUpgradeExceptionMessage } It "Should not throw exception if there is no precheck result that does not succeed and is critical" { $precheckResults = @( @{ "Severity" = "INFORMATIONAL" "Status" = "SUCCESS" }, @{ "Severity" = "WARNING" "Status" = "ERROR" } ) { Test-Precheck -Results $precheckResults -Operation "Install" } | Should -Not -Throw { Test-Precheck -Results $precheckResults -Operation "Upgrade" } | Should -Not -Throw } } Describe "Test-EventLogServiceOnNodes" { Context "When EventLog service is running on all nodes" { It "should return Success for all nodes" { Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"} Mock -ModuleName "Moc" Get-ClusterNode { @(@{ "Name" = "Node1" }, @{ "Name" = "Node2" }) } Mock -ModuleName "Moc" Invoke-Command { [PSCustomObject]@{ Status = "Running" } } $results = Test-EventLogServiceOnNodes $results | ForEach-Object { $_.TestResult | Should -Be "Success" $_.Details | Should -Match "EventLog service is running" } } } Context "When EventLog service is stopped on a node" { It "should return Failed for the node" { Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"} Mock -ModuleName "Moc" Get-ClusterNode { @(@{ "Name" = "Node1" }) } Mock -ModuleName "Moc" Invoke-Command { [PSCustomObject]@{ Status = "Stopped" } } $results = Test-EventLogServiceOnNodes $results[0].TestResult | Should -Be "Failed" $results[0].Details | Should -Match "EventLog service is not running" $results[0].Recommendation | Should -Match "Ensure EventLog service is running on all nodes. Start-Service -Name 'EventLog'" } } Context "When checking EventLog service throws an exception" { It "should return Failed with error details" { Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"} Mock -ModuleName "Moc" Get-ClusterNode { @(@{ "Name" = "Node1" }) } Mock -ModuleName "Moc" Invoke-Command { throw "Access denied" } $results = Test-EventLogServiceOnNodes $results[0].TestResult | Should -Be "Failed" $results[0].Details | Should -Match "Could not check EventLog service" $results[0].Recommendation | Should -Match "Ensure remote PowerShell access" } } Context "When EventLog service is running on one node and stopped on another" { It "should return Success for Node1 and Failed for Node2" { Mock -ModuleName "Moc" Get-FailoverCluster {"cluster"} Mock -ModuleName "Moc" Get-ClusterNode { @(@{ Name = "Node1" }, @{ Name = "Node2" }) } Mock -ModuleName "Moc" Invoke-Command { param($ComputerName, $ScriptBlock) if ($ComputerName -eq "Node1") { return [PSCustomObject]@{ Status = "Running" } } else { return [PSCustomObject]@{ Status = "Stopped" } } } $results = Test-EventLogServiceOnNodes $node1 = $results | Where-Object { $_.NodeName -eq "Node1" } $node2 = $results | Where-Object { $_.NodeName -eq "Node2" } $node1.TestResult | Should -Be "Success" $node1.Details | Should -Match "EventLog service is running" $node2.TestResult | Should -Be "Failed" $node2.Details | Should -Match "EventLog service is not running" $node2.Recommendation | Should -Match "Ensure EventLog service is running on all nodes. Start-Service -Name 'EventLog'" } } } # SIG # Begin signature block # MIInJwYJKoZIhvcNAQcCoIInGDCCJxQCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDPOqHNUQ28JWWG # 5dGQCTgyIBaxBzCU9nx334AYfDcB0aCCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghnDMIIZvwIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ # KoZIhvcNAQkEMSIEIMbZYUVMHDWnkNf21rMTb/MViC5UrIIGYXEIZDoT5mAVMEIG # CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAHo44FLqDaKhlpDV+ # IQ8uR23XaNApd1/Foxl9C3+StBi3vpNdc4FLovQtREAxuSRqqFIFPKSaV69sqgA0 # K9sawlmhqkqx/OxrVJ0ZlmaXkR54kTOhjLoM4YtxTjJls17aHGJln6uJWJjWjPfJ # 5yhQlbH2T2k6vl11oJ0PdMfMLxJKHy6t6cgc+8X0VZowZkXFHDy8HyXRzTUBkeuS # ez1wDKa6ZgLya+7ZF9PZzlkSNTZmaqH6DrLlsDrS/UwbOVZv9nhNDMUH+Wr1wH9T # iLyvaSej5G4VeDV5FbClYnFKN5xXZrPOZ3pjiSWsCzDqgIeo8oqnpxMIMKEVnLkj # 1L3YP6GCF5MwghePBgorBgEEAYI3AwMBMYIXfzCCF3sGCSqGSIb3DQEHAqCCF2ww # ghdoAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9 # MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCCiXI+VYgsw2KKx # 1MP3AtIy/qwHo5FLNHlTTFnKG2Mn3QIGahdQmW3gGBMyMDI2MDUyODAwNDE1OC42 # OThaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw # JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNVBAMT # HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHpMIIHIDCCBQigAwIBAgIT # MwAAAh86cGnkojAulQABAAACHzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt # ZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTM5NTFaFw0yNzA1MTcxOTM5NTFa # MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL # ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk # IFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l # LVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDL # O8XFOcfGqAqgiz0+AmQmFl3dZ0aTG4UFJkqqNdMHy28DaheCBs6ONufukye5x42C # WkzgRIy9kE2VWwEntZ8ZkgyrykC0bIqsID7+6FxguseTXf1Vwvm1D8104VmetoBJ # lJ4uGbuyJZUvXDx55nVh50ygLTzZ24WkQsnPpvRZv2kPc39f3bhLyHVtnHsa/W/8 # 6Vrftd+AfFveA+qN/EY+XGj5c/DPMXCYECb0arYb92dDJWtwzpyBrp4gfHlgY1UE # pc4l4AGELrf2J4wrxTzTW+SM8XhV1dOOPrYjD080IbZqL8B+IF0RCdn269YXrGK6 # QIHipznKZcCS8jN30YAHnTJVN5Zzs6t/2YsqBGDquvDad7934FFTwzvUcO3VoIyd # 93XWwvP8/SCFVJh21W8oGQTptGHyly+Fl4henVMVZF1v6osOtirX8GFTiEhnf8nR # dOg7yZYAJ0xy9CtDfbXaTn/cf3Lq3N/GCYKFjC+5mUCE+AJhmxMuMdvSUGmKiAFd # iPAjUTqsWWBBZJm0eCwgeGJFmmQA+V7/98BKcE+gUL7O9eWRDQwKeAcvo6rxNv2Y # 4jKrHA6Z/wi3a/fKUhLCNZES8qGdrpDAm7qh+6FjYxytAbkiKM6uTNy/ULPlwtlY # ZoAJDDQP7eYCywwVbNTbHXRBSS+NccC0sSB4W7U67wIDAQABo4IBSTCCAUUwHQYD # VR0OBBYEFNk72sGDlH0r5DwvfGR5XwJI8B7bMB8GA1UdIwQYMBaAFJ+nFV0AXmJd # g/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGlt # ZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0l # AQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUA # A4ICAQBlbu3IoynnPz0K1iPbeNnsej2b15l5sdl2FAFBBGT9lRdc2gNV8LAIusPY # HHhUvRDcsx4lbMNhVKPGu4TDLaqNt/CI+SFtGuqdRLpVP1XE9cCLyKrKPpcJFJCq # PpV+efoAtYBmIUQcxxwT7WIQ7gag8+rkKvrMkCoRqKS0mKv8J1sKfi85+G2uhZ/1 # RteSVdYZOZOj+Sb4wzonTCTj7EtgMN/BX35W5dTzd7wJdGepYkVi871dSrC2Tr1Z # FzAR7S44drCWZpJ6phJabVNOsNxFJKgSykugOGWzQ318Rr3MTPg2s3Bns+pUPVgM # ijd4bUOH2BlEsLMMwOcolTTZqg1HYrdY1jxpUAI9ipjBQRINL/O705Z+/f2LjNmJ # QooCVJVX24adpZ519SsfazGoqXGt91bmqKo0fI09Il4sUHh4ih6rpiQDBlyL7vmv # CejwVxYevY4qVwTZ/o3gvl+R0lFxYS9feIM4NeG0+WsDZ7jLci5MFeuNwosQY3z2 # 6Xg1oj0U9u+ncR9uTU+xBmJ8BtlCdhQ13RNMX5P+krRYPB3XCp9Jm6XaO1995q32 # AIZm1mzBGI6yHlviXaEC5TzGiO1LXuPtXZU2X93oQJbMoe3v8+5CPKrQalGWyYuh # 2a3V1pwbj+W0FEmEFPpu8TI+qYO1IIQWUSRvFjXth5Ob02hMMjCCB3EwggVZoAMC # AQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m # dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIy # NVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9 # DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2 # Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N # 7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXc # ag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJ # j361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjk # lqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37Zy # L9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M # 269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLX # pyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLU # HMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode # 2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEA # ATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYE # FJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEB # MEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # RG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEE # AYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB # /zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEug # SaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9N # aWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsG # AQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jv # b0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt # 4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsP # MeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++ # Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9 # QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2 # wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aR # AfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5z # bcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nx # t67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3 # Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+AN # uOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/Z # cGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNMMIICNAIBATCB+aGB0aSBzjCB # yzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMc # TWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBU # U1MgRVNOOjM3MDMtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T # dGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBLIMg1P7sNuCXpmbH2IXT2tXeE # EKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3 # DQEBCwUAAgUA7cHPEjAiGA8yMDI2MDUyNzIwMTQxMFoYDzIwMjYwNTI4MjAxNDEw # WjBzMDkGCisGAQQBhFkKBAExKzApMAoCBQDtwc8SAgEAMAYCAQACATIwBwIBAAIC # ElcwCgIFAO3DIJICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK # MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQsFAAOCAQEApymKn9dC # gDdZM8h40XyO6a+eNPTMGaYhXk4wjB1lucVm3+OOdodg428AJrQdh4h7fvqF3stI # Kh0pv02ARojCq/EQdBIseiFrhcDLd1osPDyae8DkV6GUoYUllHsag5gCS1mYsoXy # IJ/hi7zVCyFYpC6NOMufI2FziKWcRU8VYz1/7Avhfc2KkoUIOM1FitbYKvAGNXYP # VzGo9FrcagF54bfcqp11Tvv/LNXNcGHm+r96mbPVCHj/F8bdhgqOAadtg9aRIKSv # uM5iSvRlq6iGflQDEiRAihz1qy3CH+W+cHbMpcWtDsiK0meewDEl4j0T8NX6n5l+ # DMxvOSp3QJ9s3zGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwAhMzAAACHzpwaeSiMC6VAAEAAAIfMA0GCWCGSAFlAwQCAQUAoIIBSjAa # BgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIIrt+cw3 # yFnQ/w0Dl/Xz8EGBcTS1X4R1EeMy75EDQd69MIH6BgsqhkiG9w0BCRACLzGB6jCB # 5zCB5DCBvQQgsCQK31aQKwy1RGQW7pNjQ/dRd1GcKJi49mF7fKQt/BQwgZgwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAh86cGnkojAulQAB # AAACHzAiBCBGJKDQ2aDjA3SkStukBUN5FOZnFFjvObT3dwpZgGC+/TANBgkqhkiG # 9w0BAQsFAASCAgBxFxoengaJlSWHtYEeuGiIl1WCrLuupYl5HVD7DOqE/RyV0/4M # Es/TGD5tzJalCOYY2QWd8VYfR87v9LImIx64/5FcYLS0RQ28AEXZPBg510SrELp7 # CjhaQ53XJZ82+rGcboD1JHYYi4gp7eJrQeA1ceA6Edz6IF4V7q/exs3iUMPfeRWf # tsD1naEAaqFPoVPbAdt5lC3M7jKucj57zmXxk+HizvfpNN2UqHxrVWmIe7/UhiLJ # cBwpw6Ec1QvOPRvMBMHejbCJ7S22EiaLzWSbC1H980xtTz2Ab0WVZoYbondbJzFJ # HDxHoqmNUfGiPJEIBoKy8fDAbxGB+cNvr7390cDEJB6Nec/IDbfHUaf6It43iTsS # ZcGPwgMmXZKnrXJB3Neo0+77oN/Dmc7AhWhXOY3AekUAc+LT3rvq+flGWeHJq9Vy # Y56NPLMyrLEj5mzNBRjxmGFoaPEAIaGgYyfRSSupsjR9KpkpJJSFgN7zaBFzEMYJ # W/+Pix/64ZrunMdbLtgHUMopPPvKmVYlsi1+dIdWQV4h+nl/QIOvSh7MwSUaadmQ # WO4nSC1t/nRbZMO7h+fq07I79TOkovzMSuPPGgskLnVOjW7direPO11vvzU8Y0y+ # 3Bb18XsksBpwziJ4inzdDcGDl5h9HePUe+Pqu0aS9uSyxH5tWTP2z0rZkg== # SIG # End signature block |