diff --git a/.build/utilities/create-function.ps1 b/.build/utilities/create-core-function.ps1 similarity index 87% rename from .build/utilities/create-function.ps1 rename to .build/utilities/create-core-function.ps1 index 19bad3c..779ed6d 100644 --- a/.build/utilities/create-function.ps1 +++ b/.build/utilities/create-core-function.ps1 @@ -17,7 +17,7 @@ param ( [Parameter(Mandatory = $true)] [ArgumentCompleter({ - $possibleValues = Get-ChildItem -Path "$PSScriptRoot\templates\*" -Include *.ps1 | Select-Object -ExpandProperty Name + $possibleValues = Get-ChildItem -Path "$PSScriptRoot\templates\module\*" -Include *.ps1 | Select-Object -ExpandProperty Name return $possibleValues | ForEach-Object { $_ } })] [System.String]$Template, @@ -29,6 +29,7 @@ param ( $ErrorActionPreference = 'Stop' # verify the function name matches approved verbs +$FunctionName = (Get-Culture).TextInfo.ToTitleCase("$FunctionName".tolower()) $verb = $FunctionName.Split('-')[0] if($verb -inotin (Get-Verb).Verb){ $publicDocURL = 'https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands' @@ -36,9 +37,6 @@ if($verb -inotin (Get-Verb).Verb){ return } -# retrieve the template that will be used -$templatePath = Get-Item -Path "$PSScriptRoot\templates\$($Template)" - # generate the filepath where the function will be saved if($IsPublic){ $subPath = 'public' @@ -47,10 +45,11 @@ else { $subPath = 'private' } -$relativePath = ("src\modules\$($Module)\$($subPath)\$($FunctionName).ps1") +$templatePath = Get-Item -Path "$PSScriptRoot\templates\module\$($Template)" +$relativePath = "src\modules\{0}\{1}\{2}.ps1" -f $Module, $subPath, $FunctionName $destinationPath = Join-Path -Path "$PSScriptRoot\..\..\" -ChildPath $relativePath -$allFunctionFiles = (Get-ChildItem -Path "$PSScriptRoot\..\..\src\modules\*" -Include *.ps1 -Recurse) +$allFunctionFiles = (Get-ChildItem -Path "$PSScriptRoot\..\..\src\*" -Include *.ps1 -Recurse) $duplicateFunction = $allFunctionFiles | Where-Object {$_.BaseName -ieq $FunctionName} if($duplicateFunction){ "{0} already exists under {1}. Specify a new function name to prevent dot sourcing conflicts" -f $FunctionName, $duplicateFunction.FullName | Write-Host -ForegroundColor:Yellow @@ -60,10 +59,11 @@ if($duplicateFunction){ # create the function file based off template and replace the function name within the file $newFunctionFile = Copy-Item -Path $templatePath -Destination $destinationPath -PassThru if($newFunctionFile){ - "Successfully created {0}" -f $newFunctionFile.FullName | Write-Host $content = Get-Content -Path $newFunctionFile.FullName $newContent = $content -Replace 'VERB-NAME', $FunctionName $newContent | Set-Content -Path $newFunctionFile.FullName + + "Successfully created {0}" -f $newFunctionFile.FullName | Write-Host } else { "Unable to create new function file {0}" | Write-Host -ForegroundColor:Yellow diff --git a/.build/utilities/create-health-function.ps1 b/.build/utilities/create-health-function.ps1 new file mode 100644 index 0000000..291160e --- /dev/null +++ b/.build/utilities/create-health-function.ps1 @@ -0,0 +1,64 @@ +<# + .SYNOPSIS + Creates new function file based off template +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)] + [System.String]$FunctionName, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + $possibleValues = Get-ChildItem -Path "$PSScriptRoot\..\..\src\health" -Directory | Select-Object -ExpandProperty Name + return $possibleValues | ForEach-Object { $_ } + })] + [System.String]$Module, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + $possibleValues = Get-ChildItem -Path "$PSScriptRoot\templates\health\*" -Include *.ps1 | Select-Object -ExpandProperty Name + return $possibleValues | ForEach-Object { $_ } + })] + [System.String]$Template +) + +$ErrorActionPreference = 'Stop' + +# verify the function name matches approved verbs +$FunctionName = (Get-Culture).TextInfo.ToTitleCase("$FunctionName".tolower()) +$verb = $FunctionName.Split('-')[0] +if($verb -ine ('Test')){ + "Please ensure that you are using the verb 'Test' for your function name when creating health validation function." | Write-Host -ForegroundColor:Yellow + return +} + + +# generate the filepath where the function will be saved +$templatePath = Get-Item -Path "$PSScriptRoot\templates\health\$($Template)" +$relativePath = "src\health\{0}\{1}.ps1" -f $Module, $FunctionName +$destinationPath = Join-Path -Path "$PSScriptRoot\..\..\" -ChildPath $relativePath + +$allFunctionFiles = (Get-ChildItem -Path "$PSScriptRoot\..\..\src\*" -Include *.ps1 -Recurse) +$duplicateFunction = $allFunctionFiles | Where-Object {$_.BaseName -ieq $FunctionName} +if($duplicateFunction){ + "{0} already exists under {1}. Specify a new function name to prevent dot sourcing conflicts" -f $FunctionName, $duplicateFunction.FullName | Write-Host -ForegroundColor:Yellow + return +} + +# create the function file based off template and replace the function name within the file +$newFunctionFile = Copy-Item -Path $templatePath -Destination $destinationPath -PassThru +if($newFunctionFile){ + $content = Get-Content -Path $newFunctionFile.FullName + $newContent = $content -Replace 'VERB-NAME', $FunctionName + $newContent | Set-Content -Path $newFunctionFile.FullName + + "Successfully created {0}" -f $newFunctionFile.FullName | Write-Host +} +else { + "Unable to create new function file {0}" | Write-Host -ForegroundColor:Yellow +} + +$ErrorActionPreference = 'Continue' + + diff --git a/.build/utilities/create-knownissue-function.ps1 b/.build/utilities/create-knownissue-function.ps1 new file mode 100644 index 0000000..841c214 --- /dev/null +++ b/.build/utilities/create-knownissue-function.ps1 @@ -0,0 +1,55 @@ +<# + .SYNOPSIS + Creates new function file based off template +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)] + [System.String]$FunctionName, + + [Parameter(Mandatory = $true)] + [ArgumentCompleter({ + $possibleValues = Get-ChildItem -Path "$PSScriptRoot\templates\knownIssue\*" -Include *.ps1 | Select-Object -ExpandProperty Name + return $possibleValues | ForEach-Object { $_ } + })] + [System.String]$Template +) + +$ErrorActionPreference = 'Stop' + +# verify the function name matches approved verbs +$FunctionName = (Get-Culture).TextInfo.ToTitleCase("$FunctionName".tolower()) +if($FunctionName -inotlike "Test-SdnKI*"){ + "Please ensure that you are prefixing your function name wtih 'Test-SdnKI' when creating known issue check." | Write-Host -ForegroundColor:Yellow + return +} + +# generate the filepath where the function will be saved +$templatePath = Get-Item -Path "$PSScriptRoot\templates\knownIssue\$($Template)" +$relativePath = "src\knownIssues\{0}.ps1" -f $FunctionName +$destinationPath = Join-Path -Path "$PSScriptRoot\..\..\" -ChildPath $relativePath + +$allFunctionFiles = (Get-ChildItem -Path "$PSScriptRoot\..\..\src\*" -Include *.ps1 -Recurse) +$duplicateFunction = $allFunctionFiles | Where-Object {$_.BaseName -ieq $FunctionName} +if($duplicateFunction){ + "{0} already exists under {1}. Specify a new function name to prevent dot sourcing conflicts" -f $FunctionName, $duplicateFunction.FullName | Write-Host -ForegroundColor:Yellow + return +} + +# create the function file based off template and replace the function name within the file +$newFunctionFile = Copy-Item -Path $templatePath -Destination $destinationPath -PassThru +if($newFunctionFile){ + $content = Get-Content -Path $newFunctionFile.FullName + $newContent = $content -Replace 'VERB-NAME', $FunctionName + $newContent | Set-Content -Path $newFunctionFile.FullName + + "Successfully created {0}" -f $newFunctionFile.FullName | Write-Host +} +else { + "Unable to create new function file {0}" | Write-Host -ForegroundColor:Yellow +} + +$ErrorActionPreference = 'Continue' + + diff --git a/.build/utilities/templates/health/basic_health_template.ps1 b/.build/utilities/templates/health/basic_health_template.ps1 new file mode 100644 index 0000000..d4c5242 --- /dev/null +++ b/.build/utilities/templates/health/basic_health_template.ps1 @@ -0,0 +1,75 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function VERB-NAME { + <# + .SYNOPSIS + Validate that the configurationState and provisioningState is Success + #> + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [Uri]$NcUri = $Global:SdnDiagnostics.EnvironmentInfo.NcUrl, + + [Parameter(Mandatory = $false)] + [System.String[]]$ComputerName = $global:SdnDiagnostics.EnvironmentInfo.MUX, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $NcRestCredential = [System.Management.Automation.PSCredential]::Empty + ) + + try { + "Validating " | Trace-Output + + $config = Get-SdnRoleConfiguration -Role:SoftwareLoadBalancer + + if($null -eq $NcUri){ + throw New-Object System.NullReferenceException("Please specify NcUri parameter or execute Get-SdnInfrastructureInfo to populate environment details") + } + + if($null -eq $ComputerName){ + throw New-Object System.NullReferenceException("Please specify ComputerName parameter or execute Get-SdnInfrastructureInfo to populate environment details") + } + + # if Credential parameter not defined, check to see if global cache is populated + if(!$PSBoundParameters.ContainsKey('Credential')){ + if($Global:SdnDiagnostics.Credential){ + $Credential = $Global:SdnDiagnostics.Credential + } + } + + # if NcRestCredential parameter not defined, check to see if global cache is populated + if(!$PSBoundParameters.ContainsKey('NcRestCredential')){ + if($Global:SdnDiagnostics.NcRestCredential){ + $NcRestCredential = $Global:SdnDiagnostics.NcRestCredential + } + } + + $status = 'Success' + $arrayList = [System.Collections.ArrayList]::new() + + <# + INSERT LOGIC DETECTION HERE + IF FAILURE DETECTED, SET STATUS TO FAILURE + $status = 'Failure' + # ADD TO ARRAY LIST WITH ANY PROPERTIES THAT YOU WANT TO RETURN + #> + + return [PSCustomObject]@{ + Status = $status + Properties = $arrayList + } + + } + catch { + "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error + } +} \ No newline at end of file diff --git a/.build/utilities/templates/knownIssue/basic_knownIssue_template.ps1 b/.build/utilities/templates/knownIssue/basic_knownIssue_template.ps1 new file mode 100644 index 0000000..0bf09ee --- /dev/null +++ b/.build/utilities/templates/knownIssue/basic_knownIssue_template.ps1 @@ -0,0 +1,75 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function VERB-NAME { + <# + .SYNOPSIS + Validate that the configurationState and provisioningState is Success + #> + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [Uri]$NcUri = $Global:SdnDiagnostics.EnvironmentInfo.NcUrl, + + [Parameter(Mandatory = $false)] + [System.String[]]$ComputerName = $global:SdnDiagnostics.EnvironmentInfo.MUX, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $NcRestCredential = [System.Management.Automation.PSCredential]::Empty + ) + + try { + "Validating " | Trace-Output + + $config = Get-SdnRoleConfiguration -Role:SoftwareLoadBalancer + + if($null -eq $NcUri){ + throw New-Object System.NullReferenceException("Please specify NcUri parameter or execute Get-SdnInfrastructureInfo to populate environment details") + } + + if($null -eq $ComputerName){ + throw New-Object System.NullReferenceException("Please specify ComputerName parameter or execute Get-SdnInfrastructureInfo to populate environment details") + } + + # if Credential parameter not defined, check to see if global cache is populated + if(!$PSBoundParameters.ContainsKey('Credential')){ + if($Global:SdnDiagnostics.Credential){ + $Credential = $Global:SdnDiagnostics.Credential + } + } + + # if NcRestCredential parameter not defined, check to see if global cache is populated + if(!$PSBoundParameters.ContainsKey('NcRestCredential')){ + if($Global:SdnDiagnostics.NcRestCredential){ + $NcRestCredential = $Global:SdnDiagnostics.NcRestCredential + } + } + + $issueDetected = $false + $arrayList = [System.Collections.ArrayList]::new() + + <# + INSERT LOGIC DETECTION HERE + IF FAILURE DETECTED, SET VARIABLE TO $TRUE + $issueDetected = $true + # ADD TO ARRAY LIST WITH ANY PROPERTIES THAT YOU WANT TO RETURN + #> + + return [PSCustomObject]@{ + Result = $issueDetected + Properties = $arrayList + } + + } + catch { + "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error + } +} \ No newline at end of file diff --git a/.build/utilities/templates/basic_template.ps1 b/.build/utilities/templates/module/basic_template.ps1 similarity index 100% rename from .build/utilities/templates/basic_template.ps1 rename to .build/utilities/templates/module/basic_template.ps1 diff --git a/README.md b/README.md index 1cd84ca..c80f636 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,32 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio When contributing to this project, ensure you: 1. Review existing functions already available and reuse where possible. -1. Functions should be placed under `src\modules\[ModuleName]\[Private | Public]\Verb-FunctionName.ps1`. - - Function name should match the file name. - - Limit one function per file. - - Ensure that the file name is added to `src\SDNDiagnostics.psm1` so it is dot sourced on module import. - - Use [Approved Verbs for PowerShell Commands](https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands). -1. If your function should be exported and available after module import, be sure to add your function to the export list in `src\SDNDiagnostics.psd1` under `FunctionsToExport`. 1. Return native .NET objects whenever possible. 1. Rarely should you return from a function with format-table, format-list, etc.. If they do, they should use the PowerShell verb `Show`. 1. Environments that this module run on may be in a broken or inconcistent state, so defensive coding techniques should be leveraged. 1. Leverage `$Global:SdnDiagnostics` for caching when appropriate. +## Creating Core Modules +When creating core functions: + +1. Functions should be placed under `src\modules\[ModuleName]\[Private | Public]\Verb-FunctionName.ps1`. + - Function name should match the file name. + - Limit one function per file. + - Ensure that the file name is added to `src\SDNDiagnostics.psm1` so it is dot sourced on module import. + - Use [Approved Verbs for PowerShell Commands](https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands). +1. If your function should be exported and available after module import, be sure to add your function to the export list in `src\SdnDiagnostics.psd1` under `FunctionsToExport`. + +To help ensure consistency, leverage `.build\utilities\create-function.ps1` to help create your functions. This will create a `.ps1` file off the specified template and place into the appropriate module directory. Example: +```powershell +.\create-core-function.ps1 -FunctionName 'Disable-RasGatewayTracing' -Module Gateway -Template basic_template.ps1 -IsPublic +``` + ## Creating Health Validation Tests When creating a health validation test, ensure you: 1. Create the `ps1` file under `src\health\[role]\` as the name of the validation test. e.g. `Test-SdnServerHealth.ps1` 1. Function should return a PSCustomObject that contains the following format: - ``` + ```powershell # $status should contain either 'Success' or 'Failure' # $properties will contain any related information in scenario of 'Failure' status else { @@ -46,10 +55,16 @@ When creating a health validation test, ensure you: - `Debug-SdnFabricInfrastructure` will automatically pick up tests under the `src\health` directory. 1. The infrastructure information can be retrieved from global cache at `$Global:SdnDiagnostics` +To help ensure consistency, leverage `.build\utilities\create-health-function.ps1` to help create your functions. This will create a `.ps1` file off the specified template and place into the appropriate module under `src\health`. Example: +```powershell +.\create-health-function.ps1 -FunctionName 'Test-SdnLoadBalancerMuxOnline' -Module SoftwareLoadBalancer -Template basic_health_template.ps1 +``` + ## Creating Known Issue Tests 1. Create the `ps1` file under `src\knownIssues` as the name of the validation test. e.g. `Test-SdnKIVfpDuplicatePort.ps1` + - Ensure that you prefix your function name as `Test-SdnKI`. 1. Function should return a PSCustomObject that contains the following format: - ``` + ```powershell # $issueIdentified should either be $true or $false depending on if issue was detected # $properties will contain any related information in scenario of $true status else { @@ -63,6 +78,11 @@ When creating a health validation test, ensure you: - `Test-SdnKnownIssues` will automatically pick up tests under the `src\knownIssues` directory. 1. The infrastructure information can be retrieved from global cache at `$Global:SdnDiagnostics` +To help ensure consistency, leverage `.build\utilities\create-knownissue-function.ps1` to help create your functions. This will create a `.ps1` file off the specified template and place into the appropriate module under `src\knownIssues`. Example: +```powershell +.\create-knownissue-function.ps1 -FunctionName 'Test-SdnKIVfpDuplicatePort' -Template basic_knownIssue_template.ps1 +``` + # Build Validation and Testing 1. To generate a local build of the module, run `.\.build\build.ps1` which will generate an SdnDiagnostics module package to `~\out\build\SdnDiagnostics`. 1. Copy the module to `C:\Program Files\WindowsPowerShell\Modules`. @@ -70,12 +90,13 @@ When creating a health validation test, ensure you: 1. Install the modules to the SDN nodes in the dataplane. ```powershell $uri = 'https://NcURI' +$netController = 'NC ComputerName' $nodes = @() $nodes += (Get-SdnServer -NcUri $uri -ManagementAddress) $nodes += (Get-SdnGateway -NcUri $uri -ManagementAddress) $nodes += (Get-SdnLoadBalancerMux -NcUri $uri -ManagementAddress) -$nodes += (Get-SdnNetworkController -NetworkController 'SA20N26-NC01' -ServerNameOnly) +$nodes += (Get-SdnNetworkController -NetworkController $netController -ServerNameOnly) Install-SdnDiagnostic -ComputerName $nodes -Force ```