├── .gitignore ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── Test-NetStack.psd1 ├── Test-NetStack.psm1 ├── appveyor.yml ├── helpers ├── icmp.psm1 ├── internal.psm1 ├── ndk.psm1 ├── prerequisites.psm1 └── tcp.psm1 ├── tests ├── results │ └── TestResults.xml ├── setup │ ├── deploy.ps1 │ ├── initiate-tests.ps1 │ └── install.ps1 └── unit │ └── unit.tests.ps1 └── tools ├── CTS-Traffic └── ctsTraffic.exe └── NTttcp └── NTttcp.exe /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.ipdb 70 | *.pdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "PowerShell: Interactive Session", 9 | "type": "PowerShell", 10 | "request": "launch", 11 | "cwd": "" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/7w8d0vkx55i4a9tt?svg=true)](https://ci.appveyor.com/project/MSFTCoreNet/test-netstack) 2 | [![downloads](https://img.shields.io/powershellgallery/dt/Test-NetStack.svg?label=downloads)](https://www.powershellgallery.com/packages/Test-NetStack) 3 | 4 | # Test-NetStack: A network integration testing tool 5 | 6 | ## Synopsis 7 | 8 | Test-NetStack is a PowerShell-based testing tool that performs ICMP, TCP, and RDMA traffic testing of networks. Test-NetStack can help identify potential network (fabric and host) misconfiguration or operational instability. 9 | 10 | Test-NetStack can validate the various network data paths on Windows, testing native, synthetic, and hardware offloaded (RDMA) data paths for issues with: 11 | 12 | - Connectivity 13 | - Packet fragmention 14 | - Low throughput 15 | - Congestion 16 | 17 | ## Test Details 18 | 19 | Test-NetStack first performs connectivity mapping across a cluster, specific nodes, or IP targets then tests: 20 | 21 | - Stage1: ICMP Connectivity, Reliability, and PMTUD 22 | - Stage2: TCP Stress 1:1 23 | - Stage3: RDMA Connectivity 24 | - Stage4: RDMA Stress 1:1 25 | - Stage5: RDMA Stress N:1 26 | - Stage6: RDMA Stress N:N 27 | 28 | For more information, run: 29 | 30 | ```PowerShell 31 | help Test-NetStack 32 | ``` 33 | 34 | ## Install the Tool 35 | 36 | ```PowerShell 37 | Install-Module Test-NetStack 38 | ``` 39 | 40 | ### Run the Tool 41 | 42 | ```PowerShell 43 | $NetStackResults = Test-NetStack 44 | ``` 45 | 46 | ```PowerShell 47 | $NetStackResults = Test-Netstack -Nodes 'Node1', 'Node2', 'NodeN' 48 | ``` 49 | 50 | ```PowerShell 51 | $NetStackResults = Test-Netstack -IPTarget '192.168.1.1', '192.168.1.2', '192.168.1.3', '192.168.1.4' 52 | ``` 53 | 54 | ### Reviewing Results 55 | 56 | ```PowerShell 57 | $NetStackResults.Stage1 | ft * 58 | ``` 59 | 60 | ```PowerShell 61 | $NetStackResults.Stage2 | ft * 62 | ``` 63 | 64 | ```PowerShell 65 | $NetStackResults.Stage3 | ft * 66 | ``` 67 | 68 | ```PowerShell 69 | $NetStackResults.Stage4 | ft * 70 | ``` 71 | 72 | ```PowerShell 73 | $NetStackResults.Stage5 | ft * 74 | ``` 75 | 76 | ```PowerShell 77 | $NetStackResults.Stage6 | ft * 78 | ``` 79 | 80 | ### Testable vs Disqualified Networks 81 | 82 | Test-NetStack will identify networks that can and cannot be tested. To review the Test-NetStack networks that will be tested, use: 83 | 84 | ```PowerShell 85 | $NetStack.TestableNetworks 86 | ``` 87 | 88 | To review the Test-NetStack networks that cannot be tested. 89 | 90 | ```PowerShell 91 | $NetStack.DisqualifiedNetworks 92 | ``` 93 | 94 | ### 95 | 96 | # Contributing 97 | 98 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 99 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 100 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 101 | 102 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 103 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 104 | provided by the bot. You will only need to do this once across all repos using our CLA. 105 | 106 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 107 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 108 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 109 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /Test-NetStack.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Test-NetStack' 3 | # 4 | # Generated by: Microsoft Core Networking Team 5 | # 6 | # Generated on: 7/14/2021 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Test-NetStack.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '2021.7.14.21' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'aad51a88-4b67-b840-4c35-d914d20be9b0' 22 | 23 | # Author of this module 24 | Author = 'Microsoft Core Networking Team' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Microsoft' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2021 Inc. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Test-NetStack is a module that can be used to exercise enterprise customer network infrastructure, and, in particular, their RDMA infrastructure. In particualr, Test-NetStack is a pester-integrated powershell tool that attempts to stress and strain the network fabric in order to isolate RDMA issues and failures.' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'Test-NetStack' 73 | 74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 75 | CmdletsToExport = @() 76 | 77 | # Variables to export from this module 78 | VariablesToExport = '*' 79 | 80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 81 | AliasesToExport = @() 82 | 83 | # DSC resources to export from this module 84 | # DscResourcesToExport = @() 85 | 86 | # List of all modules packaged with this module 87 | # ModuleList = @() 88 | 89 | # List of all files packaged with this module 90 | # FileList = @() 91 | 92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 93 | PrivateData = @{ 94 | 95 | PSData = @{ 96 | 97 | # Tags applied to this module. These help with module discovery in online galleries. 98 | Tags = 'MSFTNet' 99 | 100 | # A URL to the license for this module. 101 | # LicenseUri = '' 102 | 103 | # A URL to the main website for this project. 104 | ProjectUri = 'https://github.com/microsoft/Test-NetStack' 105 | 106 | # A URL to an icon representing this module. 107 | # IconUri = '' 108 | 109 | # ReleaseNotes of this module 110 | # ReleaseNotes = '' 111 | 112 | # Prerelease string of this module 113 | # Prerelease = '' 114 | 115 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 116 | # RequireLicenseAcceptance = $false 117 | 118 | # External dependent modules of this module 119 | # ExternalModuleDependencies = @() 120 | 121 | } # End of PSData hashtable 122 | 123 | } # End of PrivateData hashtable 124 | 125 | # HelpInfo URI of this module 126 | # HelpInfoURI = '' 127 | 128 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 129 | # DefaultCommandPrefix = '' 130 | 131 | } 132 | 133 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # YAML Reference Guide : https://www.appveyor.com/docs/appveyor-yml/ 2 | # Environmental Variables Guide : https://www.appveyor.com/docs/environment-variables/ 3 | # YAML Validator : https://ci.appveyor.com/tools/validate-yaml 4 | # AppVeyor Build Pipeline : https://www.appveyor.com/docs/build-configuration/ 5 | # GitHub push with tokens : https://www.appveyor.com/docs/how-to/git-push/ 6 | 7 | # Repo cloned into this folder on the build worker 8 | clone_folder: c:\projects\Test-NetStack 9 | 10 | # Date-based versioning. 11 | # Version will be of format: yyyy.MM.dd.Build Number 12 | 13 | init: 14 | - ps: $Env:repoName = $($env:APPVEYOR_REPO_NAME.Split('/')[1]) 15 | - ps: Update-AppveyorBuild -Version "$(Get-Date -format yyyy.MM.dd).$env:appveyor_build_number" 16 | - ps: $Env:BuildVersion = "$(Get-Date -format yyyy.MM.dd).$env:appveyor_build_number" 17 | 18 | # Install script prior to running tests 19 | install: 20 | - ps: . .\tests\setup\install.ps1 21 | 22 | # Initiate tests 23 | test_script: 24 | - ps: . .\tests\setup\initiate-tests.ps1 25 | 26 | # finalize build 27 | deploy_script: 28 | - ps: . .\tests\setup\deploy.ps1 29 | 30 | version: 0.0.0.{build} 31 | 32 | image: 33 | - Visual Studio 2019 34 | 35 | # Environment variables for PowerShell Gallery (NuGetAPIKey) and GitHub (GitHubKey) API key for publishing updates 36 | # - The "secure:" value is the Appveyor encryption of the key 37 | # - GitHub update occurs to ensure that the module version is incremented based on the build number 38 | 39 | #CoreNetBuilder 40 | environment: 41 | NuGetApiKey: 42 | secure: xkyacYshVFP3IdMGFxpldw1CTVfyJCJiJvEVMi/l9W8hwiE1VL6z3Gh+EHXr1rkr 43 | GitHubKey: 44 | secure: 4VTkUGIAUqMtDSbrIrbn856kPmOLVYNSSy/NwsnQajL+7o+xpoqWZcTpYfEXco2/ 45 | # APPVEYOR_RDP_PASSWORD: 46 | # secure: 7acrwNLLvCuP7Gw0r+rLvPxbtzr8yWmJCgaKndtKjmA= 47 | 48 | # Disable automatic builds; Without this, the following error shows up: 49 | # "Specify a project or solution file. The directory does not contain a project or solution file." 50 | build: "off" 51 | 52 | max_jobs: 1 53 | 54 | # Ignore testing a commit if specific strings used in commit message: updated readme, update readme, update docs, update version, update appveyor 55 | skip_commits: 56 | message: /updated readme.*|update readme.*s|update docs.*|update version.*|update appveyor.*/ 57 | files: 58 | - README.md 59 | 60 | # There's no need to alter the build number for a Pull Request (PR) since they don't modify anything 61 | pull_requests: 62 | do_not_increment_build_number: true 63 | 64 | #on_finish: 65 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 66 | -------------------------------------------------------------------------------- /helpers/icmp.psm1: -------------------------------------------------------------------------------- 1 | Function Invoke-ICMPPMTUD { 2 | 3 | #If you change order of params, you must change the order of invoke-command params to match 4 | [CmdletBinding(DefaultParameterSetName = 'PMTUD')] 5 | param ( 6 | [Parameter(Mandatory=$true, Position=0)] 7 | [Alias("Sender","SourceIP")] 8 | [string] $Source, 9 | 10 | [Parameter(Mandatory=$true, Position=1)] 11 | [Alias("Receiver", "DestinationIP", "RemoteIP", "Target")] 12 | [string] $Destination, 13 | 14 | [Parameter(Mandatory=$false, Position=2)] 15 | [int] $StartBytes = 32, 16 | 17 | [Parameter(Mandatory=$false, Position=3)] 18 | [int] $EndBytes = 10000, 19 | 20 | [Parameter(Mandatory=$false, Position=4)] 21 | [Switch] $Reliability = $false, 22 | 23 | [Parameter(Mandatory=$false, Position=5)] 24 | [int] $Count = 1000, 25 | 26 | [Parameter(Mandatory=$false, Position=6)] 27 | [int] $testTime = 15, 28 | 29 | # Used for Write-Progress must also specify ParentID 30 | [Parameter(Mandatory=$false, Position=7)] 31 | [int] $ID, 32 | 33 | # Used for Write-Progress must also specify ID 34 | [Parameter(Mandatory=$false, Position=8)] 35 | [int] $ParentID 36 | ) 37 | 38 | #region Start-Ping: This function needs to be nested for sending remotely via Invoke-Command (e.g. Function:\Invoke-ICMPPMTU) 39 | Function Start-Ping { 40 | param ( 41 | [Parameter(Mandatory=$true, Position=0)] 42 | [Alias("Sender","SourceIP")] 43 | [string] $Source, 44 | 45 | [Parameter(Mandatory=$true, Position=1)] 46 | [Alias("Receiver", "DestinationIP", "RemoteIP", "Target")] 47 | [string] $Destination, 48 | 49 | [Parameter(Mandatory=$false, Position=2)] 50 | [int] $Size = 32, 51 | 52 | [Parameter(Mandatory=$false, Position=3)] 53 | [Switch] $RTT 54 | ) 55 | 56 | Add-Type @" 57 | using System; 58 | using System.Net; 59 | using System.Text; 60 | using System.Runtime.InteropServices; 61 | 62 | public class IcmpPing 63 | { 64 | [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] 65 | private struct ICMP_OPTIONS 66 | { 67 | public byte Ttl; 68 | public byte Tos; 69 | public byte Flags; 70 | public byte OptionsSize; 71 | public IntPtr OptionsData; 72 | } 73 | 74 | [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] 75 | private struct ICMP_ECHO_REPLY 76 | { 77 | public int Address; 78 | public int Status; 79 | public int RoundTripTime; 80 | public short DataSize; 81 | public short Reserved; 82 | public IntPtr DataPtr; 83 | public ICMP_OPTIONS Options; 84 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst=9900)] 85 | public string Data; 86 | } 87 | 88 | [DllImport("Iphlpapi.dll", SetLastError = true)] 89 | private static extern IntPtr IcmpCreateFile(); 90 | [DllImport("Iphlpapi.dll", SetLastError = true)] 91 | private static extern bool IcmpCloseHandle(IntPtr handle); 92 | [DllImport("Iphlpapi.dll", SetLastError = true)] 93 | private static extern int IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr hEvent, IntPtr apcRoutine, IntPtr apcContext, int sourceAddress, int destinationAddress, string requestData, short requestSize, ref ICMP_OPTIONS requestOptions, ref ICMP_ECHO_REPLY replyBuffer, int replySize, int timeout); 94 | 95 | public bool PingStatus(IPAddress sourceIp, IPAddress destIp, int dataSize) 96 | { 97 | IntPtr icmpHandle = IcmpCreateFile(); 98 | ICMP_OPTIONS icmpOptions = new ICMP_OPTIONS(); 99 | icmpOptions.Ttl = 255; 100 | icmpOptions.Flags = 0x02; 101 | ICMP_ECHO_REPLY icmpReply = new ICMP_ECHO_REPLY(); 102 | string sData = CreateSendData(dataSize); 103 | 104 | int replies = IcmpSendEcho2Ex(icmpHandle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, BitConverter.ToInt32(sourceIp.GetAddressBytes(), 0), BitConverter.ToInt32(destIp.GetAddressBytes(), 0), sData, (short)sData.Length, ref icmpOptions, ref icmpReply, Marshal.SizeOf(icmpReply), 30); 105 | IcmpCloseHandle(icmpHandle); 106 | 107 | if (replies > 0) 108 | { 109 | if (icmpReply.DataSize != dataSize) 110 | { 111 | return false; 112 | } 113 | 114 | return true; 115 | } 116 | 117 | return false; 118 | } 119 | 120 | public int PingRTT(IPAddress sourceIp, IPAddress destIp, int dataSize) 121 | { 122 | IntPtr icmpHandle = IcmpCreateFile(); 123 | ICMP_OPTIONS icmpOptions = new ICMP_OPTIONS(); 124 | icmpOptions.Ttl = 255; 125 | icmpOptions.Flags = 0x02; 126 | ICMP_ECHO_REPLY icmpReply = new ICMP_ECHO_REPLY(); 127 | string sData = CreateSendData(dataSize); 128 | 129 | int replies = IcmpSendEcho2Ex(icmpHandle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, BitConverter.ToInt32(sourceIp.GetAddressBytes(), 0), BitConverter.ToInt32(destIp.GetAddressBytes(), 0), sData, (short)sData.Length, ref icmpOptions, ref icmpReply, Marshal.SizeOf(icmpReply), 30); 130 | IcmpCloseHandle(icmpHandle); 131 | 132 | if (replies > 0) 133 | { 134 | return icmpReply.RoundTripTime; 135 | } 136 | 137 | return -1; 138 | } 139 | 140 | private string CreateSendData(int length) 141 | { 142 | var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 143 | var random = new Random(); 144 | StringBuilder builder = new StringBuilder(); 145 | for(int index = 0; index < length; index++) 146 | { 147 | builder.Append(chars[random.Next(chars.Length)]); 148 | } 149 | return builder.ToString(); 150 | } 151 | } 152 | "@ 153 | 154 | $pingStatus = [ICMPPing]::new() 155 | 156 | if ($RTT) { 157 | return ($pingStatus.PingRTT($Source, $Destination, $Size)) 158 | } 159 | else { 160 | return ($pingStatus.PingStatus($Source, $Destination, $Size)) 161 | } 162 | 163 | 164 | } 165 | 166 | #endregion 167 | if (-Not($Reliability)) { 168 | [int] $lastKnownGood = -1 169 | 170 | if ((Start-Ping -Destination $Destination -Source $Source -Size $StartBytes)) { 171 | # update LKG 172 | $lastKnownGood = $StartBytes 173 | 174 | # last failed ping size 175 | $lastFailed = $EndBytes 176 | 177 | # next ping will be somewhere between start and end 178 | [int]$nextPing = [math]::Round(($EndBytes - $StartBytes) / 2, 0) 179 | 180 | # controls whether we found the MTU or not 181 | $MtuFound = $false 182 | 183 | :FindMTU while (-NOT $MtuFound) { 184 | do { 185 | Write-Verbose "nextPing: $nextPing, LKG: $lastKnownGood, LF: $lastFailed" 186 | $PingTest = Start-Ping -Destination $Destination -Source $Source -Size $nextPing 187 | 188 | if ($PingTest) { $failedCounter = 0 } 189 | else { $failedCounter = $failedCounter + 1 } 190 | 191 | } until ($PingTest -or $failedCounter -gt 3) 192 | 193 | 194 | # make payload smaller 195 | if (-NOT $PingTest) { 196 | # save the failed ping size 197 | $lastFailed = $nextPing 198 | 199 | # find the nextPing 200 | $nextPing = [math]::Round(($nextPing + $lastKnownGood) / 2, 0) 201 | 202 | if ($nextPing -ge $lastKnownGood) { 203 | $nextPing = [math]::Round(($lastFailed + $nextPing) / 2, 0) 204 | } 205 | 206 | Write-Verbose "NextPing: $nextPing" 207 | } 208 | else { # ping worked, but we're not done; make payload larger 209 | $LKG = $nextPing 210 | 211 | $nextPing = [math]::Round(($lastFailed + $lastKnownGood) / 2, 0) 212 | 213 | if ($nextPing -le $lastKnownGood) 214 | { 215 | $nextPing = [math]::Round(($lastFailed + $nextPing) / 2, 0) 216 | } 217 | 218 | $lastKnownGood = $LKG 219 | Write-Verbose "NextPing: $nextPing" 220 | } 221 | 222 | Write-Verbose "LastFailed: $lastFailed `n`n" 223 | 224 | # we should reach a point where nextping should be LKG + 1... then we're done 225 | if ($nextPing -eq $lastKnownGood) { 226 | $MtuFound = $true 227 | Write-Verbose "All done!" 228 | } 229 | } 230 | } 231 | 232 | if ($lastKnownGood -ne -1) { 233 | <# 234 | MTU = Payload + headers 235 | MSS = Payload 236 | 237 | ICMP headers are: 238 | 239 | Ethernet = 14 Bytes 240 | IP = 20 Bytes 241 | ICMP = 8 Bytes 242 | 243 | Total = 42 Bytes 244 | #> 245 | 246 | $obj = New-Object -TypeName psobject 247 | $obj | Add-Member -MemberType NoteProperty -Name Connectivity -Value $true 248 | $obj | Add-Member -MemberType NoteProperty -Name MSS -Value $lastKnownGood 249 | $obj | Add-Member -MemberType NoteProperty -Name MTU -Value $($lastKnownGood + 42) 250 | 251 | return $obj 252 | } 253 | else { # ping failed for some reason 254 | Write-Verbose "There were no successful pings. Please make sure ping (ICMP Echo) is permitted to $Destination." 255 | 256 | $obj = New-Object -TypeName psobject 257 | $obj | Add-Member -MemberType NoteProperty -Name Connectivity -Value $false 258 | $obj | Add-Member -MemberType NoteProperty -Name MSS -Value '0' 259 | $obj | Add-Member -MemberType NoteProperty -Name MTU -Value '0' 260 | 261 | return $obj 262 | } 263 | } 264 | else { # If we already know the MTU, we can send a bunch at that size to see how reliable the link is 265 | $ICMPResponse = @() 266 | 267 | $testCompleted = 0 268 | $startTime = [System.DateTime]::Now 269 | 270 | do { 271 | #Specify the RTT Switch. We'll use this for more stuff later. 272 | $ICMPResponse += Start-Ping -Source $Source -Destination $Destination -Size $StartBytes -RTT 273 | } until([System.DateTime]::Now -ge $startTime.AddSeconds($testTime)) 274 | 275 | Return $ICMPResponse 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /helpers/internal.psm1: -------------------------------------------------------------------------------- 1 | #region Analysis 2 | Class MTU { 3 | [int] $MSS = '1472' 4 | 5 | MTU () {} 6 | } 7 | 8 | Class Reliability { 9 | # Min # of ICMP packets per path for a reliability test 10 | [int] $ICMPSent = '2000' 11 | 12 | # Minimum success percentage for a pass 13 | [int] $ICMPReliability = '99' 14 | 15 | # Minimum success percentage for a pass 16 | [int] $ICMPPacketLoss = '95' 17 | 18 | # Maximum Milliseconds for a pass 19 | [int] $ICMPLatency = '1.5' 20 | 21 | # Maximum jitter 22 | [Double] $ICMPJitter = '.1' 23 | 24 | Reliability () {} 25 | } 26 | 27 | Class TCPPerf { 28 | # Min TPUT by % of link speed 29 | [int] $TPUT = '85' 30 | 31 | TCPPerf () {} 32 | } 33 | 34 | Class NDKPerf { 35 | # Min TPUT by % of link speed 36 | # Lowering to 70% while we are still in the tuning phase, return to 90% once we are more confident 37 | [int] $TPUT = '70' 38 | 39 | NDKPerf () {} 40 | } 41 | 42 | 43 | # Stuff All Analysis Classes in Here 44 | Class Analyzer { 45 | $MTU = [MTU]::new() 46 | $Reliability = [Reliability]::new() 47 | $TCPPerf = [TCPPerf]::new() 48 | $NDKPerf = [NDKPerf]::new() 49 | 50 | Analyzer () {} 51 | } 52 | #endregion Analysis 53 | 54 | #region DataTypes 55 | Class InterfaceDetails { 56 | [string] $Node 57 | [string] $InterfaceAlias 58 | [string] $InterfaceIndex 59 | [String] $IPAddress 60 | [String] $PrefixLength 61 | [String] $AddressState 62 | 63 | [String] $Network 64 | [String] $Subnet 65 | [String] $SubnetMask 66 | [String] $VLAN 67 | 68 | [string] $VMNetworkAdapterName 69 | } 70 | #endregion DataTypes 71 | 72 | #region Non-Exported Helpers 73 | Function Convert-CIDRToMask { 74 | param ( 75 | [Parameter(Mandatory = $true)] 76 | [int] $PrefixLength 77 | ) 78 | 79 | $bitString = ('1' * $prefixLength).PadRight(32, '0') 80 | 81 | [String] $MaskString = @() 82 | 83 | for($i = 0; $i -lt 32; $i += 8){ 84 | $byteString = $bitString.Substring($i,8) 85 | $MaskString += "$([Convert]::ToInt32($byteString, 2))." 86 | } 87 | 88 | Return $MaskString.TrimEnd('.') 89 | } 90 | 91 | Function Convert-MaskToCIDR { 92 | param ( 93 | [Parameter(Mandatory = $true)] 94 | [IPAddress] $SubnetMask 95 | ) 96 | 97 | [String] $binaryString = @() 98 | $SubnetMask.GetAddressBytes() | ForEach-Object { $binaryString += [Convert]::ToString($_, 2) } 99 | 100 | Return $binaryString.TrimEnd('0').Length 101 | } 102 | 103 | Function Convert-IPv4ToInt { 104 | Param ( 105 | [Parameter(Mandatory = $true)] 106 | [IPAddress] $IPv4Address 107 | ) 108 | 109 | $bytes = $IPv4Address.GetAddressBytes() 110 | 111 | Return [System.BitConverter]::ToUInt32($bytes,0) 112 | } 113 | 114 | Function Convert-IntToIPv4 { 115 | Param ( 116 | [Parameter(Mandatory = $true)] 117 | [uint32]$Integer 118 | ) 119 | 120 | $bytes = [System.BitConverter]::GetBytes($Integer) 121 | 122 | Return ([IPAddress]($bytes)).ToString() 123 | } 124 | #endregion Non-Exported Helpers 125 | 126 | #region Helper Functions 127 | Function Write-LogMessage { 128 | Param ( 129 | $Message, 130 | $LogFile 131 | ) 132 | Write-Host "[$([System.DateTime]::Now)] $Message" 133 | "[$([System.DateTime]::Now)] $Message" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 134 | } 135 | 136 | # Storage Intent Deployment Status: 137 | # NotDeployed: Network ATC is not in use - Test-NetStack will test RDMA based on whether RDMA is enabled on a given adapter. 138 | # DeploymentSuccess: Network ATC has been deployed successfully in a configuration suitable for RDMA testing. Test-NetStack will test RDMA based on whether an adapter belongs to a storage intent. 139 | # DeploymentFail: Network ATC has been deployed, but was either unsuccessful or the configuration does not support RDMA. Test-NetStack will mark all requested RDMA stages as failures. 140 | enum StorageIntentDeploymentStatus { 141 | NotDeployed 142 | DeploymentSuccess 143 | DeploymentFail 144 | } 145 | 146 | # This function will determine: 147 | # 1. Is Network ATC deployed? 148 | # 2. Are all intents successfully deployed? 149 | # 3. Has a storage intent been? 150 | # and return an object containing the cluster name, storage intent, and one of the 3 storage intent deployment statuses to tell the Test-NetStack function how to test RDMA stages. 151 | Function Get-StorageIntentDeploymentStatus { 152 | param ($LogFile) 153 | 154 | Write-LogMessage -Message "Determining if Network ATC is deployed in a supported configuration" -LogFile $LogFile 155 | 156 | # Create PSObject to store cluster name, storage intent, and deployment status 157 | $StorageIntentDeployment = New-Object -TypeName psobject 158 | $StorageIntentDeployment | Add-Member -MemberType NoteProperty -Name ClusterName -Value $null 159 | $StorageIntentDeployment | Add-Member -MemberType NoteProperty -Name StorageIntent -Value $null 160 | $StorageIntentDeployment | Add-Member -MemberType NoteProperty -Name DeploymentStatus -Value [StorageIntentDeploymentStatus]::NotDeployed 161 | 162 | # Check for cluster since will need name for Network ATC commands on older builds 163 | try { 164 | $ClusterName = Get-Cluster -ErrorAction Stop 165 | $StorageIntentDeployment.ClusterName = $ClusterName 166 | } 167 | catch { 168 | Write-LogMessage -Message "Warning: Cluster not found. If this is unexpected, please check for errors in cluster deployment. Otherwise, Test-NetStack will continue in standalone mode, and perform a best-effort test of RDMA based on Get-NetAdapterRDMA Enabled property." -LogFile $LogFile 169 | Return $StorageIntentDeployment 170 | } 171 | 172 | # If a cluster name was returned, proceed with Network ATC configuration check 173 | if (-not [String]::IsNullOrEmpty($ClusterName)) { 174 | try { 175 | $NetIntentStatusTimeout = 5 176 | $StartTime = Get-Date 177 | $EndTime = (Get-Date).AddMinutes($NetIntentStatusTimeout) 178 | do { 179 | # Check for net intent status to determine 1. is Network ATC being used and 2. are all intents successfully deployed 180 | $NetIntentStatus = Get-NetIntentStatus -ClusterName $ClusterName 181 | $IntentsContainFailures = ($NetIntentStatus | Where-Object ConfigurationStatus -eq "Failed" | Measure-Object).Count -gt 0 182 | if ($IntentsContainFailures) { 183 | Write-LogMessage -Message "At least one intent failed to be deployed. Please investigate Network ATC configuration. Test-NetStack will mark any requested RDMA stages as failures." -LogFile $LogFile 184 | $StorageIntentDeployment.DeploymentStatus = [StorageIntentDeploymentStatus]::DeploymentFail 185 | Return $StorageIntentDeployment 186 | } 187 | 188 | # 'Retrying' status indicates an attempt to recover from a failure, so success is unlikely - bail after only one minute 189 | $IntentsContainRetrying = ($NetIntentStatus | Where-Object ConfigurationStatus -eq "Retrying" | Measure-Object).Count -gt 0 190 | if ($IntentsContainRetrying -and ((Get-Date) - $StartTime).TotalSeconds -ge 60) { 191 | Write-LogMessage -Message "At least one intent failed to recover from 'Retrying' status after one minute. Please investigate Network ATC configuration. Test-NetStack will mark any requested RDMA stages as failures." -LogFile $LogFile 192 | $StorageIntentDeployment.DeploymentStatus = [StorageIntentDeploymentStatus]::DeploymentFail 193 | Return $StorageIntentDeployment 194 | } 195 | 196 | # Statuses such as 'Validating', 'Pending', 'Provisioning', and 'ProvisioningUpdate' have a good chance of resolving on their own - retry status check for up to five minutes 197 | $AllIntentsSuccessful = ($NetIntentStatus | Where-Object ConfigurationStatus -ne "Success" | Measure-Object).Count -eq 0 198 | if ($AllIntentsSuccessful -eq $false) { 199 | Write-LogMessage -Message "Some intents do not have a successful configuration status. Checking again in 60 seconds to allow temporary statuses to resolve." -LogFile $LogFile 200 | $NetIntentStatus | ft * | Out-File $LogFile -Append -Encoding utf8 -Width 2000 201 | Sleep 60 202 | } 203 | } while ($AllIntentsSuccessful -eq $false -and (Get-Date) -le $EndTime) 204 | 205 | if ($AllIntentsSuccessful -eq $false) { 206 | Write-LogMessage -Message "Intents not successfully deployed after 5 minutes. Test-NetStack will mark any requested RDMA stages as failures." -LogFile $LogFile 207 | $StorageIntentDeployment.DeploymentStatus = [StorageIntentDeploymentStatus]::DeploymentFail 208 | Return $StorageIntentDeployment 209 | } else { 210 | Write-LogMessage -Message "All intents deployed successfully." -LogFile $LogFile 211 | } 212 | 213 | } 214 | catch { 215 | # Get-NetIntentStatus will throw an error if Network ATC is not configured, in which case we are here 216 | Write-LogMessage -Message "Warning: No net intent found. If this is unexpected, please check for errors in Network ATC deployment. Otherwise, Test-NetStack will perform a best-effort test of RDMA based on Get-NetAdapterRDMA Enabled property." -LogFile $LogFile 217 | Return $StorageIntentDeployment 218 | } 219 | 220 | # If we've made it to this point, Network ATC is in use and all intents are successfully deployed 221 | $StorageIntent = Get-NetIntent -ClusterName $ClusterName | Where IsStorageIntentSet -eq $true 222 | if ($StorageIntent.Count -eq 1) { 223 | Write-LogMessage -Message "Storage intent set." -LogFile $LogFile 224 | $StorageIntentDeployment.StorageIntent = $StorageIntent 225 | $RDMAOverride = (Get-NetIntent -ClusterName $ClusterName).AdapterAdvancedParametersOverride.NetworkDirect 226 | if ($RDMAOverride -eq $false -or $RDMAOverride -eq 0) { 227 | Write-LogMessage -Message "RDMA has been disabled with an override. If this is unexpected, please submit an override enabling the Network Direct adapter property. 228 | Otherwise, Test-NetStack will mark any requested RDMA stages as failures.`r 229 | To submit override enabling Network Direct:`r 230 | `$AdapterOverride = New-NetIntentAdapterPropertyOverrides`r 231 | `$AdapterOverride.NetworkDirect = 0`r 232 | Set-NetIntent -Name -AdapterPropertyOverrides `$AdapterOverride -ClusterName " -LogFile $LogFile 233 | $StorageIntentDeployment.DeploymentStatus = [StorageIntentDeploymentStatus]::DeploymentFail 234 | Return $StorageIntentDeployment 235 | } 236 | } else { 237 | Write-LogMessage -Message "No storage intent set. Test-NetStack will mark any requested RDMA stages as failures." -LogFile $LogFile 238 | $StorageIntentDeployment.DeploymentStatus = [StorageIntentDeploymentStatus]::DeploymentFail 239 | Return $StorageIntentDeployment 240 | } 241 | 242 | # If we've made it to this point, Network ATC is in use, all intents are successfully deployed, and a storage intent is defined 243 | Write-LogMessage -Message "Network ATC is deployed and successfully configured with a storage intent. Test-NetStack will perform RDMA testing according to net intents." -LogFile $LogFile 244 | $StorageIntentDeployment.DeploymentStatus = [StorageIntentDeploymentStatus]::DeploymentSuccess 245 | Return $StorageIntentDeployment 246 | } 247 | } 248 | 249 | Function Get-StorageIntentNICMapping { 250 | param ($StorageIntentDeployment) 251 | 252 | # Go through output of Get-NetIntentAllGoalStates to determine which adapters are associated with a storage intent, and pNIC/vNIC mapping 253 | $IntentAllGoalStates = Get-NetIntentAllGoalStates -ClusterName $StorageIntentDeployment.ClusterName 254 | $NodeNames = (Get-ClusterNode).Name 255 | $StorageNICMapping = @() 256 | foreach ($NodeName in $NodeNames) { 257 | if ($StorageIntentDeployment.StorageIntent.IsOnlyStorage) { 258 | # If an intent is storage only, no vNICs will be created 259 | $IntentAllGoalStates.$NodeName.$($StorageIntentDeployment.StorageIntent.IntentName).SwitchConfig.NetAdapters | ForEach-Object { 260 | $HostNICMapping = New-Object -TypeName psobject 261 | $HostNICMapping | Add-Member -MemberType NoteProperty -Name NodeName -Value $NodeName 262 | $HostNICMapping | Add-Member -MemberType NoteProperty -Name pNIC -Value $_.NetAdapterName 263 | $HostNICMapping | Add-Member -MemberType NoteProperty -Name vNIC -Value $null 264 | $StorageNICMapping += $HostNICMapping 265 | } 266 | } else { 267 | # If an intent is not storage only, store the pNIC/vNIC mappings 268 | $IntentAllGoalStates.$NodeName.$($StorageIntentDeployment.StorageIntent.IntentName).SwitchConfig.StorageVirtualNetworkAdapters | ForEach-Object { 269 | $HostNICMapping = New-Object -TypeName psobject 270 | $HostNICMapping | Add-Member -MemberType NoteProperty -Name NodeName -Value $NodeName 271 | $HostNICMapping | Add-Member -MemberType NoteProperty -Name pNIC -Value $_.TeamedPhysicalAdapterName 272 | $HostNICMapping | Add-Member -MemberType NoteProperty -Name vNIC -Value $_.VmAdapterName 273 | $StorageNICMapping += $HostNICMapping 274 | } 275 | } 276 | 277 | } 278 | Return $StorageNICMapping 279 | } 280 | Function Get-ConnectivityMapping { 281 | param ( 282 | [string[]] $Nodes, 283 | [string[]] $IPTarget, 284 | $StorageIntentNICMapping 285 | ) 286 | 287 | #TODO: Add IP Target disqualification if the addressState not eq not preferred 288 | 289 | $Mapping = @() 290 | foreach ($IP in $IPTarget) { 291 | $thisNode = (Resolve-DnsName -Name $IP -DnsOnly).NameHost.Split('.')[0] 292 | 293 | if ($thisNode) { # Resolution Available 294 | if ($thisNode -eq $env:COMPUTERNAME) { 295 | $AdapterIP = Get-NetIPAddress -IPAddress $IP -AddressFamily IPv4 -SuffixOrigin Dhcp, Manual -AddressState Preferred, Invalid, Duplicate | 296 | Select InterfaceAlias, InterfaceIndex, IPAddress, PrefixLength, AddressState 297 | 298 | # Remove APIPA 299 | $AdapterIP = $AdapterIP | Where IPAddress -NotLike '169.254.*' 300 | 301 | $NetAdapter = Get-NetAdapter -InterfaceIndex $AdapterIP.InterfaceIndex 302 | 303 | $VMNetworkAdapter = Get-VMNetworkAdapter -ManagementOS | Where DeviceID -in $NetAdapter.DeviceID 304 | 305 | $RDMAAdapter = Get-NetAdapterRdma -Name "*" | Where-Object -FilterScript { $_.Enabled } | Select-Object -ExpandProperty Name 306 | } 307 | else { 308 | # Do Not use Invoke-Command here. In the current build nested properties are not preserved and become strings 309 | $AdapterIP = Get-NetIPAddress -IPAddress $IP -CimSession $thisNode -AddressFamily IPv4 -SuffixOrigin Dhcp, Manual -AddressState Preferred | 310 | Select InterfaceAlias, InterfaceIndex, IPAddress, PrefixLength, AddressState 311 | 312 | # Remove APIPA 313 | $AdapterIP = $AdapterIP | Where IPAddress -NotLike '169.254.*' 314 | 315 | $NetAdapter = Get-NetAdapter -CimSession $thisNode -InterfaceIndex $AdapterIP.InterfaceIndex 316 | $VMNetworkAdapter = Get-VMNetworkAdapter -CimSession $thisNode -ManagementOS | Where DeviceID -in $NetAdapter.DeviceID 317 | $RDMAAdapter = Get-NetAdapterRdma -CimSession $thisNode -Name "*" | Where-Object -FilterScript { $_.Enabled } | Select-Object -ExpandProperty Name 318 | } 319 | 320 | $ClusRes = Get-ClusterResource -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Where { $_.OwnerGroup -eq 'Cluster Group' -and $_.ResourceType -eq 'IP Address' } 321 | $ClusterIPs = ($ClusRes | Get-ClusterParameter -ErrorAction SilentlyContinue -Name Address).Value 322 | 323 | $NodeOutput = @() 324 | foreach ($thisAdapterIP in ($AdapterIP | Where IPAddress -NotIn $ClusterIPs)) { 325 | $Result = New-Object -TypeName psobject 326 | $thisNetAdapter = $NetAdapter | Where InterfaceIndex -eq $thisAdapterIP.InterfaceIndex 327 | $thisVMNetworkAdapter = $VMNetworkAdapter | Where DeviceID -EQ $thisNetAdapter.DeviceID 328 | 329 | $Result | Add-Member -MemberType NoteProperty -Name NodeName -Value $thisNode 330 | $Result | Add-Member -MemberType NoteProperty -Name InterfaceAlias -Value $thisAdapterIP.InterfaceAlias 331 | $Result | Add-Member -MemberType NoteProperty -Name InterfaceIndex -Value $thisAdapterIP.InterfaceIndex 332 | $Result | Add-Member -MemberType NoteProperty -Name IPAddress -Value $thisAdapterIP.IPAddress 333 | $Result | Add-Member -MemberType NoteProperty -Name PrefixLength -Value $thisAdapterIP.PrefixLength 334 | $Result | Add-Member -MemberType NoteProperty -Name AddressState -Value $thisAdapterIP.AddressState 335 | $Result | Add-Member -MemberType NoteProperty -Name InterfaceDescription -Value $thisNetAdapter.InterfaceDescription 336 | $Result | Add-Member -MemberType NoteProperty -Name LinkSpeed -Value $thisNetAdapter.LinkSpeed 337 | 338 | if ($thisNetAdapter.Name -in $RDMAAdapter) { 339 | $Result | Add-Member -MemberType NoteProperty -Name RDMAEnabled -Value $true 340 | } else { 341 | $Result | Add-Member -MemberType NoteProperty -Name RDMAEnabled -Value $false 342 | } 343 | 344 | $SubnetMask = Convert-CIDRToMask -PrefixLength $thisAdapterIP.PrefixLength 345 | $SubNetInInt = Convert-IPv4ToInt -IPv4Address $SubnetMask 346 | $IPInInt = Convert-IPv4ToInt -IPv4Address $thisAdapterIP.IPAddress 347 | 348 | $Network = Convert-IntToIPv4 -Integer ($SubNetInInt -band $IPInInt) 349 | $Subnet = "$($Network)/$($thisAdapterIP.PrefixLength)" 350 | 351 | $Result | Add-Member -MemberType NoteProperty -Name SubnetMask -Value $SubnetMask 352 | $Result | Add-Member -MemberType NoteProperty -Name Network -Value $Network 353 | $Result | Add-Member -MemberType NoteProperty -Name Subnet -Value $Subnet 354 | 355 | if ($thisVMNetworkAdapter) { 356 | $Result | Add-Member -MemberType NoteProperty -Name VMNetworkAdapterName -Value $thisVMNetworkAdapter.Name 357 | 358 | if ($thisVMNetworkAdapter.IsolationSetting.IsolationMode -eq 'VLAN') { 359 | $VLAN = $thisVMNetworkAdapter.IsolationSetting.DefaultIsolationID 360 | } 361 | elseif ($thisVMNetworkAdapter.VlanSetting.OperationMode -eq 'Access') { 362 | $VLAN = $thisVMNetworkAdapter.VlanSetting.AccessVlanId 363 | } 364 | elseif ($thisVMNetworkAdapter.IsolationSetting.IsolationMode -eq 'None' -and 365 | $thisVMNetworkAdapter.VlanSetting.OperationMode -eq 'Untagged') { 366 | $VLAN = '0' 367 | } 368 | else { $thisInterfaceDetails.VLAN = 'Unsupported by Test-NetStack' } 369 | } 370 | else { 371 | $Result | Add-Member -MemberType NoteProperty -Name VMNetworkAdapterName -Value 'Not Applicable' 372 | 373 | if ($thisNetAdapter.VlanID -in 0..4095) { $VLAN = $thisNetAdapter.VlanID } 374 | else { $VLAN = 'Unsupported' } # In this case, the adapter does not support VLANs 375 | } 376 | 377 | $Result | Add-Member -MemberType NoteProperty -Name VLAN -Value $VLAN 378 | 379 | $NodeOutput += $Result 380 | } 381 | } 382 | else { # No DNS Available; we should never get here if the prerequisites do their job 383 | throw 'DNS Not available; required for remoting and to identify realistic system expectations.' 384 | } 385 | 386 | $Mapping += $NodeOutput 387 | Remove-Variable AdapterIP -ErrorAction SilentlyContinue 388 | Remove-Variable RDMAAdapter -ErrorAction SilentlyContinue 389 | } 390 | 391 | foreach ($thisNode in $Nodes) { 392 | if ($thisNode -eq $env:COMPUTERNAME) { 393 | $AdapterIP = Get-NetIPAddress -AddressFamily IPv4 -SuffixOrigin Dhcp, Manual -AddressState Preferred, Invalid, Duplicate | 394 | Select InterfaceAlias, InterfaceIndex, IPAddress, PrefixLength, AddressState 395 | 396 | # Remove APIPA 397 | $AdapterIP = $AdapterIP | Where IPAddress -NotLike '169.254.*' 398 | 399 | $NetAdapter = Get-NetAdapter -InterfaceIndex $AdapterIP.InterfaceIndex 400 | 401 | $VMNetworkAdapter = Get-VMNetworkAdapter -ManagementOS | Where DeviceID -in $NetAdapter.DeviceID 402 | 403 | $RDMAAdapter = Get-NetAdapterRdma -Name "*" | Where-Object -FilterScript { $_.Enabled } | Select-Object -ExpandProperty Name 404 | } 405 | else { 406 | # Do Not use Invoke-Command here. In the current build nested properties are not preserved and become strings 407 | $AdapterIP = Get-NetIPAddress -CimSession $thisNode -AddressFamily IPv4 -SuffixOrigin Dhcp, Manual -AddressState Preferred | 408 | Select InterfaceAlias, InterfaceIndex, IPAddress, PrefixLength, AddressState 409 | 410 | # Remove APIPA 411 | $AdapterIP = $AdapterIP | Where IPAddress -NotLike '169.254.*' 412 | 413 | $NetAdapter = Get-NetAdapter -CimSession $thisNode -InterfaceIndex $AdapterIP.InterfaceIndex 414 | $VMNetworkAdapter = Get-VMNetworkAdapter -CimSession $thisNode -ManagementOS | Where DeviceID -in $NetAdapter.DeviceID 415 | $RDMAAdapter = Get-NetAdapterRdma -CimSession $thisNode -Name "*" | Where-Object -FilterScript { $_.Enabled } | Select-Object -ExpandProperty Name 416 | } 417 | 418 | $ClusRes = Get-ClusterResource -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Where { $_.OwnerGroup -eq 'Cluster Group' -and $_.ResourceType -eq 'IP Address' } 419 | $ClusterIPs = ($ClusRes | Get-ClusterParameter -ErrorAction SilentlyContinue -Name Address).Value 420 | 421 | $NodeOutput = @() 422 | foreach ($thisAdapterIP in ($AdapterIP | Where IPAddress -NotIn $ClusterIPs)) { 423 | $Result = New-Object -TypeName psobject 424 | $thisNetAdapter = $NetAdapter | Where InterfaceIndex -eq $thisAdapterIP.InterfaceIndex 425 | $thisVMNetworkAdapter = $VMNetworkAdapter | Where DeviceID -EQ $thisNetAdapter.DeviceID 426 | 427 | $Result | Add-Member -MemberType NoteProperty -Name NodeName -Value $thisNode 428 | $Result | Add-Member -MemberType NoteProperty -Name InterfaceAlias -Value $thisAdapterIP.InterfaceAlias 429 | $Result | Add-Member -MemberType NoteProperty -Name InterfaceIndex -Value $thisAdapterIP.InterfaceIndex 430 | $Result | Add-Member -MemberType NoteProperty -Name IPAddress -Value $thisAdapterIP.IPAddress 431 | $Result | Add-Member -MemberType NoteProperty -Name PrefixLength -Value $thisAdapterIP.PrefixLength 432 | $Result | Add-Member -MemberType NoteProperty -Name AddressState -Value $thisAdapterIP.AddressState 433 | $Result | Add-Member -MemberType NoteProperty -Name InterfaceDescription -Value $thisNetAdapter.InterfaceDescription 434 | $Result | Add-Member -MemberType NoteProperty -Name LinkSpeed -Value $thisNetAdapter.LinkSpeed 435 | 436 | if ($thisNetAdapter.Name -in $RDMAAdapter) { 437 | $Result | Add-Member -MemberType NoteProperty -Name RDMAEnabled -Value $true 438 | } else { 439 | $Result | Add-Member -MemberType NoteProperty -Name RDMAEnabled -Value $false 440 | } 441 | 442 | # If we've passed in a storage intent NIC mapping, add a property for StorageIntentSet - this will be how we filter for testing in RDMA stages 443 | if ($StorageIntentNICMapping.Count -gt 0) { 444 | if ($thisNetAdapter.Name -in ($StorageIntentNICMapping | Where NodeName -eq $thisNode).pNIC -or $thisNetAdapter.Name -in ($StorageIntentNICMapping | Where NodeName -eq $thisNode).vNIC) { 445 | $Result | Add-Member -MemberType NoteProperty -Name StorageIntentSet -Value $true 446 | } else { 447 | $Result | Add-Member -MemberType NoteProperty -Name StorageIntentSet -Value $false 448 | } 449 | } 450 | 451 | $SubnetMask = Convert-CIDRToMask -PrefixLength $thisAdapterIP.PrefixLength 452 | $SubNetInInt = Convert-IPv4ToInt -IPv4Address $SubnetMask 453 | $IPInInt = Convert-IPv4ToInt -IPv4Address $thisAdapterIP.IPAddress 454 | 455 | $Network = Convert-IntToIPv4 -Integer ($SubNetInInt -band $IPInInt) 456 | $Subnet = "$($Network)/$($thisAdapterIP.PrefixLength)" 457 | 458 | $Result | Add-Member -MemberType NoteProperty -Name SubnetMask -Value $SubnetMask 459 | $Result | Add-Member -MemberType NoteProperty -Name Network -Value $Network 460 | $Result | Add-Member -MemberType NoteProperty -Name Subnet -Value $Subnet 461 | 462 | if ($thisVMNetworkAdapter) { 463 | $Result | Add-Member -MemberType NoteProperty -Name VMNetworkAdapterName -Value $thisVMNetworkAdapter.Name 464 | 465 | if ($thisVMNetworkAdapter.IsolationSetting.IsolationMode -eq 'VLAN') { 466 | $VLAN = $thisVMNetworkAdapter.IsolationSetting.DefaultIsolationID 467 | } 468 | elseif ($thisVMNetworkAdapter.VlanSetting.OperationMode -eq 'Access') { 469 | $VLAN = $thisVMNetworkAdapter.VlanSetting.AccessVlanId 470 | } 471 | elseif ($thisVMNetworkAdapter.IsolationSetting.IsolationMode -eq 'None' -and 472 | $thisVMNetworkAdapter.VlanSetting.OperationMode -eq 'Untagged') { 473 | $VLAN = '0' 474 | } 475 | else { $thisInterfaceDetails.VLAN = 'Unsupported by Test-NetStack' } 476 | } 477 | else { 478 | $Result | Add-Member -MemberType NoteProperty -Name VMNetworkAdapterName -Value 'Not Applicable' 479 | 480 | if ($thisNetAdapter.VlanID -in 0..4095) { $VLAN = $thisNetAdapter.VlanID } 481 | else { $VLAN = 'Unsupported' } # In this case, the adapter does not support VLANs 482 | } 483 | 484 | $Result | Add-Member -MemberType NoteProperty -Name VLAN -Value $VLAN 485 | 486 | $NodeOutput += $Result 487 | } 488 | 489 | $Mapping += $NodeOutput 490 | Remove-Variable AdapterIP -ErrorAction SilentlyContinue 491 | Remove-Variable RDMAAdapter -ErrorAction SilentlyContinue 492 | } 493 | 494 | Return $Mapping 495 | } 496 | 497 | Function Get-TestableNetworksFromMapping { 498 | param ( $Mapping ) 499 | 500 | $VLANSupportedNets = $Mapping | Where-Object VLAN -ne 'Unsupported' | Group-Object Subnet, VLAN 501 | $UsableNetworks = $VLANSupportedNets | Where-Object { 502 | $_.Count -ge 1 -and 503 | (($_.Group.NodeName | Select-Object -Unique).Count) -eq $($Mapping.NodeName | Select-Object -Unique).Count } 504 | 505 | if ($UsableNetworks) { Return $UsableNetworks } 506 | else { Return 'None Available' } 507 | } 508 | 509 | Function Get-DisqualifiedNetworksFromMapping { 510 | param ( $Mapping ) 511 | 512 | $VLANSupportedNets = $Mapping | Where-Object VLAN -ne 'Unsupported' | Group-Object Subnet, VLAN 513 | 514 | $DisqualifiedByInterfaceCount = $VLANSupportedNets | Where-Object Count -eq 1 515 | 516 | $DisqualifiedByNetworkAsymmetry = $VLANSupportedNets | Where-Object { $_.Count -ge 1 -and 517 | (($_.Group.NodeName | Select -Unique).Count) -ne $($Mapping.NodeName | Select -Unique).Count } 518 | 519 | $DisqualifiedByVLANSupport = $Mapping | Where-Object VLAN -eq 'Unsupported' | Group-Object Subnet, VLAN 520 | 521 | $Disqualified = New-Object -TypeName psobject 522 | if ($DisqualifiedByVLANSupport) { 523 | $Disqualified | Add-Member -MemberType NoteProperty -Name NoVLANOnInterface -Value $DisqualifiedByVLANSupport 524 | } 525 | 526 | if ($DisqualifiedByInterfaceCount) { 527 | $Disqualified | Add-Member -MemberType NoteProperty -Name OneInterfaceInSubnet -Value $DisqualifiedByInterfaceCount 528 | } 529 | 530 | if ($DisqualifiedByNetworkAsymmetry) { 531 | $Disqualified | Add-Member -MemberType NoteProperty -Name AsymmetricNetwork -Value $DisqualifiedByNetworkAsymmetry 532 | } 533 | 534 | Return $Disqualified 535 | } 536 | 537 | Function Get-VDiskStatus { 538 | param ( $LogFile ) 539 | 540 | $UnhealthyDisks = @() 541 | Write-Host "Getting Virtual Disk Health..." 542 | "Getting Virtual Disk Health..." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 543 | 544 | Get-VirtualDisk | ForEach-Object { 545 | if ($_.HealthStatus -eq 'Unhealthy') { 546 | $UnhealthyDisks += $_.FriendlyName 547 | } 548 | } 549 | if ($UnhealthyDisks.Length -gt 0) { 550 | Write-Host "$($UnhealthyDisks) are unhealthy." 551 | "$($UnhealthyDisks) are unhealthy." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 552 | return $true 553 | } 554 | else { 555 | Write-Host "All virtual disks are healthy." 556 | "All virtual disks are healthy." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 557 | return $false 558 | } 559 | } 560 | 561 | Function Get-RunspaceGroups { 562 | param ( $TestableNetworks ) 563 | # create list of all valid source->target pairs 564 | $allPairs = @() 565 | $TestableNetworks | ForEach-Object { 566 | $thisTestableNet = $_ 567 | $thisTestableNet.Group | ForEach-Object { 568 | $thisSource = $_ 569 | $thisTestableNet.Group | Where-Object NodeName -ne $thisSource.NodeName | ForEach-Object { 570 | $thisTarget = $_ 571 | $thisPair = New-Object -TypeName psobject 572 | $thisPair | Add-Member -MemberType NoteProperty -Name Source -Value $thisSource 573 | $thisPair | Add-Member -MemberType NoteProperty -Name Target -Value $thisTarget 574 | $allPairs += $thisPair 575 | } 576 | } 577 | } 578 | 579 | # build up groups of pairs that can be run simultaneously - no common elements 580 | $runspaceGroups = @() 581 | while ($allPairs -ne $null) { 582 | $allPairs | ForEach-Object { 583 | $thisPair = $_ 584 | $added = $false 585 | for ($i = 0; $i -lt $runspaceGroups.Count; $i++) { 586 | $invalidGroup = $false 587 | foreach ($pair in $runspaceGroups[$i]) { 588 | if (($pair.Source -eq $thisPair.Source) -or ($pair.Target -eq $thisPair.Target) -or ($pair.Source -eq $thisPair.Target) -or ($pair.Target -eq $thisPair.Source)) { 589 | $invalidGroup = $true 590 | } 591 | } 592 | if (!$invalidGroup -and !$added) { 593 | $runspaceGroups[$i] += $thisPair 594 | $added = $true 595 | } 596 | } 597 | if (!$added) { 598 | $runspaceGroups += , @($thisPair) 599 | } 600 | $allPairs = $allPairs -ne $thisPair 601 | } 602 | } 603 | 604 | Return $runspaceGroups 605 | } 606 | 607 | Function Get-Jitter { 608 | <# 609 | .SYNOPSIS 610 | This function takes input as a list of roundtriptimes and returns the jitter 611 | #> 612 | 613 | param ( 614 | [String[]] $RoundTripTime 615 | ) 616 | 617 | 0..($RoundTripTime.Count - 1) | ForEach-Object { 618 | $Iteration = $_ 619 | 620 | $Difference = $RoundTripTime[$Iteration] - $RoundTripTime[$Iteration + 1] 621 | $RTTDif += [Math]::Abs($Difference) 622 | } 623 | 624 | return ($RTTDif / $RoundTripTime.Count).ToString('.#####') 625 | } 626 | 627 | Function Get-Latency { 628 | <# 629 | .SYNOPSIS 630 | This function takes input as a list of roundtriptimes and returns the latency 631 | 632 | .Description 633 | This function assumes that input is in ms. Since LAT must be > 0 and ICMP only provides ms precision, we normalize 0 to 1s 634 | This function assumes that all input was successful. Scrub input before sending to this function. 635 | #> 636 | 637 | param ( 638 | [String[]] $RoundTripTime 639 | ) 640 | 641 | $RTTNormalized = @() 642 | $RTTNormalized = $RoundTripTime -replace 0, 1 643 | $RTTNormalized | ForEach-Object { [int] $RTTNumerator = $RTTNumerator + $_ } 644 | 645 | return ($RTTNumerator / $RTTNormalized.Count).ToString('.###') 646 | 647 | } 648 | 649 | 650 | Function Get-Failures { 651 | param ( $NetStackResults ) 652 | $HostNames = $NetStackResults.TestableNetworks.Group.NodeName | Select-Object -Unique 653 | $Interfaces = $NetStackResults.TestableNetworks.Group.IPAddress | Select-Object -Unique 654 | $Failures = New-Object -TypeName psobject 655 | $NetStackResults.PSObject.Properties | ForEach-Object { 656 | if ($_.Name -like 'Stage1') { 657 | $Stage1Results = $_.Value 658 | 659 | $IndividualFailures = @() 660 | $AllFailures = $Stage1Results | Where-Object PathStatus -eq Fail 661 | $AllFailures | ForEach-Object { 662 | $IndividualFailures += "($($_.SourceHostName)) $($_.Source) -> $($_.Destination)" 663 | } 664 | 665 | $InterfaceFailures = @() 666 | $Interfaces | ForEach-Object { 667 | $thisInterface = $_ 668 | $thisInterfaceResults = $Stage1Results | Where-Object Source -eq $thisInterface 669 | if ($thisInterfaceResults.PathStatus -notcontains "Pass") { 670 | $InterfaceFailures += $thisInterface 671 | } 672 | } 673 | 674 | $MachineFailures = @() 675 | $HostNames | ForEach-Object { 676 | $thisHost = $_ 677 | $thisMachineResults = $Stage1Results | Where-Object SourceHostName -eq $thisHost 678 | if ($thisMachineResults.PathStatus -notcontains "Pass") { 679 | $MachineFailures += $thisHost 680 | } 681 | } 682 | 683 | $Stage1Failures = New-Object -TypeName psobject 684 | $Stage1HadFailures = $false 685 | if ($IndividualFailures.Count -gt 0) { 686 | $Stage1Failures | Add-Member -MemberType NoteProperty -Name IndividualFailures -Value $IndividualFailures 687 | $Stage1HadFailures = $true 688 | } 689 | if ($InterfaceFailures.Count -gt 0) { 690 | $Stage1Failures | Add-Member -MemberType NoteProperty -Name InterfaceFailures -Value $InterfaceFailures 691 | $Stage1HadFailures = $true 692 | } 693 | if ($MachineFailures.Count -gt 0) { 694 | $Stage1Failures | Add-Member -MemberType NoteProperty -Name MachineFailures -Value $MachineFailures 695 | $Stage1HadFailures = $true 696 | } 697 | if ($Stage1HadFailures) { 698 | $Failures | Add-Member -MemberType NoteProperty -Name Stage1 -Value $Stage1Failures 699 | } 700 | } elseif (($_.Name -like 'Stage2') -or ($_.Name -like 'Stage3') -or ($_.Name -like 'Stage4')) { 701 | $StageResults = $_.Value 702 | $IndividualFailures = @() 703 | $AllFailures = $StageResults | Where-Object PathStatus -eq Fail 704 | $AllFailures | ForEach-Object { 705 | $IndividualFailures += "$($_.Sender) -> $($_.Receiver) ($($_.ReceiverHostName))" 706 | } 707 | 708 | $InterfaceFailures = @() 709 | $Interfaces | ForEach-Object { 710 | $thisInterface = $_ 711 | $thisInterfaceResults = $StageResults | Where-Object Receiver -eq $thisInterface 712 | if ($thisInterfaceResults.PathStatus -notcontains "Pass") { 713 | $InterfaceFailures += $thisInterface 714 | } 715 | } 716 | 717 | $MachineFailures = @() 718 | $HostNames | ForEach-Object { 719 | $thisHost = $_ 720 | $thisMachineResults = $StageResults | Where-Object ReceiverHostName -eq $thisHost 721 | if ($thisMachineResults.PathStatus -notcontains "Pass") { 722 | $MachineFailures += $thisHost 723 | } 724 | } 725 | 726 | $StageFailures = New-Object -TypeName psobject 727 | $StageHadFailures = $false 728 | if ($IndividualFailures.Count -gt 0) { 729 | $StageFailures | Add-Member -MemberType NoteProperty -Name IndividualFailures -Value $IndividualFailures 730 | $StageHadFailures = $true 731 | } 732 | if ($InterfaceFailures.Count -gt 0) { 733 | $StageFailures | Add-Member -MemberType NoteProperty -Name InterfaceFailures -Value $InterfaceFailures 734 | $StageHadFailures = $true 735 | } 736 | if ($MachineFailures.Count -gt 0) { 737 | $StageFailures | Add-Member -MemberType NoteProperty -Name MachineFailures -Value $MachineFailures 738 | $StageHadFailures = $true 739 | } 740 | if ($StageHadFailures) { 741 | $Failures | Add-Member -MemberType NoteProperty -Name $_.Name -Value $StageFailures 742 | } 743 | } elseif ($_.Name -like 'Stage5') { 744 | $StageResults = $_.Value 745 | 746 | $InterfaceFailures = @() 747 | $Interfaces | ForEach-Object { 748 | $thisInterface = $_ 749 | $thisInterfaceResults = $StageResults | Where-Object Receiver -eq $thisInterface 750 | if ($thisInterfaceResults.ReceiverStatus -notcontains "Pass") { 751 | $InterfaceFailures += $thisInterface 752 | } 753 | } 754 | 755 | $MachineFailures = @() 756 | $HostNames | ForEach-Object { 757 | $thisHost = $_ 758 | $thisMachineResults = $StageResults | Where-Object ReceiverHostName -eq $thisHost 759 | if ($thisMachineResults.ReceiverStatus -notcontains "Pass") { 760 | $MachineFailures += $thisHost 761 | } 762 | } 763 | 764 | $StageFailures = New-Object -TypeName psobject 765 | $StageHadFailures = $false 766 | if ($InterfaceFailures.Count -gt 0) { 767 | $StageFailures | Add-Member -MemberType NoteProperty -Name InterfaceFailures -Value $InterfaceFailures 768 | $StageHadFailures = $true 769 | } 770 | if ($MachineFailures.Count -gt 0) { 771 | $StageFailures | Add-Member -MemberType NoteProperty -Name MachineFailures -Value $MachineFailures 772 | $StageHadFailures = $true 773 | } 774 | if ($StageHadFailures) { 775 | $Failures | Add-Member -MemberType NoteProperty -Name $_.Name -Value $StageFailures 776 | } 777 | } elseif ($_.Name -like 'Stage6') { 778 | $StageResults = $_.Value 779 | 780 | $NetworkFailures = @() 781 | $AllFailures = $StageResults | Where-Object NetworkStatus -eq Fail 782 | $AllFailures | ForEach-Object { 783 | $NetworkFailures += "Subnet $($_.subnet) VLAN $($_.VLAN)" 784 | } 785 | 786 | $StageFailures = New-Object -TypeName psobject 787 | $StageHadFailures = $false 788 | if ($NetworkFailures.Count -gt 0) { 789 | $StageFailures | Add-Member -MemberType NoteProperty -Name NetworkFailures -Value $NetworkFailures 790 | $StageHadFailures = $true 791 | } 792 | if ($StageHadFailures) { 793 | $Failures | Add-Member -MemberType NoteProperty -Name $_.Name -Value $StageFailures 794 | } 795 | } elseif ($_.Name -like 'Stage7') { 796 | $StageResults = $_.Value 797 | 798 | $InterfaceFailures = @() 799 | $Interfaces | ForEach-Object { 800 | $thisInterface = $_ 801 | $thisInterfaceResults = $StageResults | Where-Object Receiver -eq $thisInterface 802 | if ($thisInterfaceResults.ReceiverStatus -notcontains "Pass") { 803 | $InterfaceFailures += $thisInterface 804 | } 805 | } 806 | 807 | $MachineFailures = @() 808 | $HostNames | ForEach-Object { 809 | $thisHost = $_ 810 | $thisMachineResults = $StageResults | Where-Object ReceiverHostName -eq $thisHost 811 | if ($thisMachineResults.ReceiverStatus -notcontains "Pass") { 812 | $MachineFailures += $thisHost 813 | } 814 | } 815 | 816 | $StageFailures = New-Object -TypeName psobject 817 | $StageHadFailures = $false 818 | if ($InterfaceFailures.Count -gt 0) { 819 | $StageFailures | Add-Member -MemberType NoteProperty -Name InterfaceFailures -Value $InterfaceFailures 820 | $StageHadFailures = $true 821 | } 822 | if ($MachineFailures.Count -gt 0) { 823 | $StageFailures | Add-Member -MemberType NoteProperty -Name MachineFailures -Value $MachineFailures 824 | $StageHadFailures = $true 825 | } 826 | if ($StageHadFailures) { 827 | $Failures | Add-Member -MemberType NoteProperty -Name $_.Name -Value $StageFailures 828 | } 829 | } 830 | } 831 | Return $Failures 832 | } 833 | 834 | 835 | Function Write-RecommendationsToLogFile { 836 | param ( 837 | $NetStackResults, 838 | $LogFile 839 | ) 840 | 841 | "Failure Recommendations`n" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 842 | 843 | $ModuleBase = (Get-Module Test-NetStack -ListAvailable | Select-Object -First 1).ModuleBase 844 | 845 | $NetStackResults.PSObject.Properties | Where-Object { $_.Name -like 'Stage*' } | ForEach-Object { 846 | if ($NetStackResults.Failures.PSObject.Properties.Name -contains $_.Name) { 847 | "$($_.Name) Failure Recommendations`n" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 848 | switch ($_.Name) { 849 | 'Stage1' { 850 | if ($NetStackResults.Failures.Stage1.PSObject.Properties.Name -contains "IndividualFailures") { 851 | "Individual Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 852 | "Connectivity and PMTUD failed across the following connections:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 853 | $NetStackResults.Failures.Stage1.IndividualFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 854 | "Verify subnet, VLAN, and MTU settings for relevant NICs." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 855 | } 856 | if ($NetStackResults.Failures.Stage1.PSObject.Properties.Name -contains "InterfaceFailures") { 857 | "`nInterface Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 858 | "Connectivity and PMTUD failed across all target NICs for the following source NICs:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 859 | $NetStackResults.Failures.Stage1.InterfaceFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 860 | "Verify subnet, VLAN, and MTU settings for relevant NICs. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 861 | } 862 | if ($NetStackResults.Failures.Stage1.PSObject.Properties.Name -contains "MachineFailures") { 863 | "`nMachine Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 864 | "Connectivity and PMTUD failed across all target machines for the following source machines:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 865 | $NetStackResults.Failures.Stage1.MachineFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 866 | "Verify firewall and MTU settings for the erring machines. If the problem persists, consider checking the machine cabling, NIC cabling, or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 867 | } 868 | } 869 | 'Stage2' { 870 | if ($NetStackResults.Failures.Stage2.PSObject.Properties.Name -contains "IndividualFailures") { 871 | "Individual Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 872 | "TCP throughput failed to meet threshold across the following connections:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 873 | $NetStackResults.Failures.Stage2.IndividualFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 874 | "Retry TCP transaction with repro commands. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 875 | "Receiver Repro Command: $ModuleBase\tools\NTttcp\NTttcp.exe -listen: -Protocol:tcp -buffer:262144 -transfer:21474836480 -Pattern:push -TimeLimit:30000" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 876 | "Sender Repro Command: $ModuleBase\tools\NTttcp\NTttcp.exe -target: -bind: -Connections:64 -Iterations:1 -Protocol:tcp -buffer:262144 -transfer:21474836480 -Pattern:push" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 877 | } 878 | if ($NetStackResults.Failures.Stage2.PSObject.Properties.Name -contains "InterfaceFailures") { 879 | "`nInterface Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 880 | "TCP throughput failed to meet threshold across all source NICs for the following target NICs:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 881 | $NetStackResults.Failures.Stage2.InterfaceFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 882 | "Verify NIC provisioning. Inspect VMQ, VMMQ, and RSS settings. Verify firewall settings for the erring machine. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 883 | } 884 | if ($NetStackResults.Failures.Stage2.PSObject.Properties.Name -contains "MachineFailures") { 885 | "`nMachine Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 886 | "TCP throughput failed to meet threshold across all source machines for the following target machines:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 887 | $NetStackResults.Failures.Stage2.MachineFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 888 | "Verify NIC provisioning. Inspect VMQ, VMMQ, and RSS settings. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 889 | } 890 | } 891 | 'Stage3' { 892 | if ($NetStackResults.Failures.Stage3.PSObject.Properties.Name -contains "IndividualFailures") { 893 | "Individual Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 894 | "NDK Ping failed across the following connections:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 895 | $NetStackResults.Failures.Stage3.IndividualFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 896 | "Retry NDK Ping with repro commands. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 897 | "Receiver Repro Command: NdkPerfCmd.exe -S -ServerAddr :9000 -ServerIf -TestType rping -W 15 2>&1" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 898 | "Sender Repro Command: NdkPerfCmd.exe -C -ServerAddr :9000 -ClientAddr -ClientIf -TestType rping 2>&1" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 899 | } 900 | if ($NetStackResults.Failures.Stage3.PSObject.Properties.Name -contains "InterfaceFailures") { 901 | "`nInterface Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 902 | "NDK Ping failed across all source NICs for the following target NICs:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 903 | $NetStackResults.Failures.Stage3.InterfaceFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 904 | "Verify NIC provisioning. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 905 | } 906 | if ($NetStackResults.Failures.Stage3.PSObject.Properties.Name -contains "MachineFailures") { 907 | "`nMachine Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 908 | "NDK Ping failed across all source machines for the following target machines:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 909 | $NetStackResults.Failures.Stage3.MachineFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 910 | "Verify NIC provisioning. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 911 | } 912 | } 913 | 'Stage4' { 914 | if ($NetStackResults.Failures.Stage4.PSObject.Properties.Name -contains "IndividualFailures") { 915 | "Individual Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 916 | "NDK Perf (1:1) failed across the following connections:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 917 | $NetStackResults.Failures.Stage4.IndividualFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 918 | "Retry NDK Perf (1:1) with repro commands. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 919 | "Receiver Repro Command: NDKPerfCmd.exe -S -ServerAddr :9000 -ServerIf -TestType rperf -W 20 2>&1" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 920 | "Sender Repro Command: NDKPerfCmd.exe -C -ServerAddr :9000 -ClientAddr -ClientIf -TestType rperf 2>&1" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 921 | } 922 | if ($NetStackResults.Failures.Stage4.PSObject.Properties.Name -contains "InterfaceFailures") { 923 | "`nInterface Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 924 | "NDK Perf (1:1) failed across all source NICs for the following target NICs:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 925 | $NetStackResults.Failures.Stage4.InterfaceFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 926 | "Verify NIC RDMA provisioning and traffic class settings. Consider confirming NIC firmware and drivers, as well. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 927 | } 928 | if ($NetStackResults.Failures.Stage4.PSObject.Properties.Name -contains "MachineFailures") { 929 | "`nMachine Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 930 | "NDK Perf (1:1) failed across all source machines for the following target machines:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 931 | $NetStackResults.Failures.Stage4.MachineFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 932 | "Verify NIC RDMA provisioning and traffic class settings. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 933 | } 934 | } 935 | 'Stage5' { 936 | if ($NetStackResults.Failures.Stage5.PSObject.Properties.Name -contains "InterfaceFailures") { 937 | "Interface Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 938 | "NDK Perf (N:1) failed for the following target NICs:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 939 | $NetStackResults.Failures.Stage5.InterfaceFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 940 | "Verify NIC RDMA provisioning and traffic class settings. Consider confirming NIC firmware and drivers, as well. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 941 | } 942 | if ($NetStackResults.Failures.Stage5.PSObject.Properties.Name -contains "MachineFailures") { 943 | "`nMachine Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 944 | "NDK Perf (N:1) failed across all source machines for the following target machines:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 945 | $NetStackResults.Failures.Stage5.MachineFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 946 | "Verify NIC RDMA provisioning and traffic class settings. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 947 | } 948 | } 949 | 'Stage6' { 950 | if ($NetStackResults.Failures.Stage6.PSObject.Properties.Name -contains "NetworkFailures") { 951 | "Network Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 952 | "NDK Perf (N:N) failed for networks with the following subnet/VLAN:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 953 | $NetStackResults.Failures.Stage6.NetworkFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 954 | "Verify NIC RDMA provisioning and traffic class settings. Consider confirming NIC firmware and drivers, as well. If the problem persists, consider checking NIC cabling or physical interlinks." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 955 | } 956 | } 957 | 'Stage7' { 958 | if ($NetStackResults.Failures.Stage7.PSObject.Properties.Name -contains "InterfaceFailures") { 959 | "Interface Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 960 | "RDMA Perf VMSwitch Stress failed for the following target NICs:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 961 | $NetStackResults.Failures.Stage7.InterfaceFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 962 | "Verify NIC RDMA provisioning and traffic class settings. Confirm NIC firmware, drivers, and check NIC cabling or physical interlinks as well." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 963 | } 964 | if ($NetStackResults.Failures.Stage7.PSObject.Properties.Name -contains "MachineFailures") { 965 | "`nMachine Failure Recommendations" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 966 | "RDMA Perf VMSwitch Stress failed across all source machines for the following target machines:" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 967 | $NetStackResults.Failures.Stage7.MachineFailures | Out-File $LogFile -Append -Encoding utf8 -Width 2000 968 | "Check your VMSwitch configuration and verify NIC RDMA provisioning and traffic class settings." | Out-File $LogFile -Append -Encoding utf8 -Width 2000 969 | } 970 | } 971 | } 972 | "`n" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 973 | } 974 | "####################################`r`n" | Out-File $LogFile -Append -Encoding utf8 -Width 2000 975 | } 976 | } 977 | #endregion Helper Functions 978 | -------------------------------------------------------------------------------- /helpers/ndk.psm1: -------------------------------------------------------------------------------- 1 | function Invoke-NDKPing { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory=$true, Position=0)] 5 | [PSObject] $Server, 6 | 7 | [Parameter(Mandatory=$true, Position=1)] 8 | [PSObject] $Client, 9 | 10 | [Parameter(Mandatory=$true, Position=2)] 11 | $NDKLog 12 | ) 13 | 14 | $NDKPingResults = New-Object -TypeName psobject 15 | 16 | $ServerOutput = Start-Job -ScriptBlock { 17 | param ([string] $ServerName, [string] $ServerIP, [string] $ServerIF) 18 | 19 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 20 | param([string] $ServerIP, [string] $ServerIF) 21 | cmd /c "NdkPerfCmd.exe -S -ServerAddr $($ServerIP):9000 -ServerIf $ServerIF -TestType rping -W 15 2>&1" 22 | } -ArgumentList $ServerIP, $ServerIF 23 | } -ArgumentList $Server.NodeName, $Server.IPAddress, $Server.InterfaceIndex 24 | 25 | $ClientOutput = Invoke-Command -ComputerName $Client.NodeName -ScriptBlock { 26 | param ([string] $ServerIP, [string] $ClientIP, [string] $ClientIF) 27 | 28 | cmd /c "NdkPerfCmd.exe -C -ServerAddr $($ServerIP):9000 -ClientAddr $ClientIP -ClientIf $ClientIF -TestType rping 2>&1" 29 | } -ArgumentList $Server.IPAddress, $Client.IPAddress, $Client.InterfaceIndex 30 | 31 | $ServerOutput = Receive-Job $ServerOutput -Wait -AutoRemoveJob 32 | 33 | "NDK Ping Server Output $($Server.IPAddress): " | Out-File $NDKLog -Append 34 | $ServerOutput | ForEach-Object { 35 | $ServerSuccess = $_ -match 'completes' 36 | if ($_) { $_ | Out-File $NDKLog -Append } 37 | } 38 | 39 | $NDKPingResults | Add-Member -MemberType NoteProperty -Name ServerSuccess -Value $ServerSuccess 40 | Return $NDKPingResults 41 | } 42 | 43 | function Invoke-NDKPerf1to1 { 44 | [CmdletBinding()] 45 | param ( 46 | [Parameter(Mandatory=$true, Position=0)] 47 | [PSObject] $Server, 48 | 49 | [Parameter(Mandatory=$true, Position=1)] 50 | [PSObject] $Client, 51 | 52 | [Parameter(Mandatory=$true, Position=2)] 53 | [int] $ExpectedTPUT, 54 | 55 | [Parameter(Mandatory=$true, Position=3)] 56 | $NDKServerLog, 57 | 58 | [Parameter(Mandatory=$true, Position=4)] 59 | $NDKClientLog 60 | ) 61 | 62 | $NDKPerf1to1Results = New-Object -TypeName psobject 63 | 64 | $ServerLinkSpeed = $Server.LinkSpeed.split(" ") 65 | Switch($ServerLinkSpeed[1]) { 66 | ("Gbps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) * [Math]::Pow(10, 9) / 8} 67 | ("Mbps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) * [Math]::Pow(10, 6) / 8} 68 | ("Kbps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) * [Math]::Pow(10, 3) / 8} 69 | ("bps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) / 8} 70 | } 71 | 72 | $ClientLinkSpeed = $Client.LinkSpeed.split(" ") 73 | Switch($ClientLinkSpeed[1]) { 74 | ("Gbps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) * [Math]::Pow(10, 9) / 8} 75 | ("Mbps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) * [Math]::Pow(10, 6) / 8} 76 | ("Kbps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) * [Math]::Pow(10, 3) / 8} 77 | ("bps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) / 8} 78 | } 79 | 80 | $ExpectedTPUTDec = $ExpectedTPUT / 100 81 | 82 | $Success = $False 83 | $Retries = 3 84 | 85 | while ((-not $Success) -and ($Retries -gt 0)) { 86 | $Success = $False 87 | $ServerSuccess = $False 88 | $ClientSuccess = $False 89 | 90 | $ServerCounter = Start-Job -ScriptBlock { 91 | param ([string] $ServerName, [string] $ServerInterfaceDescription ) 92 | 93 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 94 | param( [string] $ServerInterfaceDescription ) 95 | 96 | Get-Counter -Counter "\RDMA Activity($ServerInterfaceDescription)\RDMA Inbound Bytes/sec" -MaxSamples 20 -ErrorAction Ignore 97 | } -ArgumentList $ServerInterfaceDescription 98 | } -ArgumentList $Server.NodeName, $Server.InterfaceDescription 99 | 100 | $ServerOutput = Start-Job -ScriptBlock { 101 | param ([string] $ServerName, [string] $ServerIP, [string] $ServerIF) 102 | 103 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 104 | param ([string] $ServerIP, [string] $ServerIF) 105 | 106 | cmd /c "NDKPerfCmd.exe -S -ServerAddr $($ServerIP):9000 -ServerIf $ServerIF -TestType rperf -W 20 2>&1" 107 | } -ArgumentList $ServerIP,$ServerIF 108 | } -ArgumentList $Server.NodeName, $Server.IPAddress, $Server.InterfaceIndex 109 | 110 | $ClientCounter = Start-Job -ScriptBlock { 111 | param ([string] $ClientName, [string] $ClientInterfaceDescription) 112 | 113 | Invoke-Command -ComputerName $ClientName -ScriptBlock { 114 | param ([string] $ClientInterfaceDescription) 115 | 116 | Get-Counter -Counter "\RDMA Activity($ClientInterfaceDescription)\RDMA Outbound Bytes/sec" -MaxSamples 20 117 | } -ArgumentList $ClientInterfaceDescription 118 | 119 | } -ArgumentList $Client.NodeName, $Client.InterfaceDescription 120 | 121 | $ClientOutput = Start-Job -ScriptBlock { 122 | param ([string] $ClientName, [string] $ServerIP, [string] $ClientIP, [string] $ClientIF) 123 | 124 | Invoke-Command -ComputerName $ClientName -ScriptBlock { 125 | param ([string] $ServerIP, [string] $ClientIP, [string] $ClientIF) 126 | 127 | cmd /c "NDKPerfCmd.exe -C -ServerAddr $($ServerIP):9000 -ClientAddr $ClientIP -ClientIf $ClientIF -TestType rperf 2>&1" 128 | } -ArgumentList $ServerIP, $ClientIP, $ClientIF 129 | } -ArgumentList $Client.NodeName, $Server.IPAddress, $Client.IPAddress, $Client.InterfaceIndex 130 | 131 | $read = Receive-Job $ServerCounter -Wait -AutoRemoveJob 132 | $written = Receive-Job $ClientCounter -Wait -AutoRemoveJob 133 | 134 | $FlatServerOutput = $read.Readings.split(":") | ForEach-Object { 135 | try {[uint64]($_)} catch{} 136 | } 137 | $FlatClientOutput = $written.Readings.split(":") | ForEach-Object { 138 | try {[uint64]($_)} catch{} 139 | } 140 | $ServerBytesPerSecond = ($FlatServerOutput | Measure-Object -Maximum).Maximum 141 | $ClientBytesPerSecond = ($FlatClientOutput | Measure-Object -Maximum).Maximum 142 | 143 | $ServerOutput = Receive-Job $ServerOutput -Wait -AutoRemoveJob 144 | $ClientOutput = Receive-Job $ClientOutput -Wait -AutoRemoveJob 145 | 146 | "NDK Ping Server Output $($Server.IPAddress): " | Out-File $NDKServerLog -Append 147 | $ServerOutput | ForEach-Object { 148 | $ServerSuccess = $_ -match 'completes' 149 | if ($_) { Write-Host $_; $_ | Out-File $NDKServerLog -Append } 150 | } 151 | 152 | "NDK Ping Client Output $($Client.IPAddress): " | Out-File $NDKClientLog -Append 153 | $ClientOutput | ForEach-Object { 154 | if ($_) { Write-Host $_; $_ | Out-File $NDKClientLog -Append } 155 | } 156 | 157 | $MinLinkSpeedBps = ($ServerLinkSpeedBps, $ClientLinkSpeedBps | Measure-Object -Minimum).Minimum 158 | $Success = ($ServerBytesPerSecond -gt $MinLinkSpeedBps * $ExpectedTPUTDec) -and ($ClientBytesPerSecond -gt $MinLinkSpeedBps * $ExpectedTPUTDec) 159 | $Retries-- 160 | } 161 | 162 | $RawData = New-Object -TypeName psobject 163 | $RawData | Add-Member -MemberType NoteProperty -Name ServerBytesPerSecond -Value $ServerBytesPerSecond 164 | $RawData | Add-Member -MemberType NoteProperty -Name ClientBytesPerSecond -Value $ClientBytesPerSecond 165 | $RawData | Add-Member -MemberType NoteProperty -Name MinLinkSpeedBps -Value $MinLinkSpeedBps 166 | 167 | $ReceiverLinkSpeedGbps = [Math]::Round(($ServerLinkSpeedBps * 8) * [Math]::Pow(10, -9), 2) 168 | $ReceivedGbps = [Math]::Round(($ServerBytesPerSecond * 8) * [Math]::Pow(10, -9), 2) 169 | $ReceivedPctgOfLinkSpeed = [Math]::Round(($ReceivedGbps / $ReceiverLinkSpeedGbps) * 100, 2) 170 | 171 | $NDKPerf1to1Results | Add-Member -MemberType NoteProperty -Name ReceiverLinkSpeedGbps -Value $ReceiverLinkSpeedGbps 172 | $NDKPerf1to1Results | Add-Member -MemberType NoteProperty -Name ReceivedGbps -Value $ReceivedGbps 173 | $NDKPerf1to1Results | Add-Member -MemberType NoteProperty -Name ReceivedPctgOfLinkSpeed -Value $ReceivedPctgOfLinkSpeed 174 | $NDKPerf1to1Results | Add-Member -MemberType NoteProperty -Name RawData -Value $RawData 175 | 176 | Return $NDKPerf1to1Results 177 | } 178 | 179 | function Invoke-NDKPerfNto1 { 180 | [CmdletBinding()] 181 | param ( 182 | [Parameter(Mandatory=$true, Position=0)] 183 | [PSObject] $Server, 184 | 185 | [Parameter(Mandatory=$true, Position=1)] 186 | [PSObject] $ClientNetwork, 187 | 188 | [Parameter(Mandatory=$true, Position=2)] 189 | [int] $ExpectedTPUT 190 | ) 191 | 192 | $NDKPerfNto1Results = New-Object -TypeName psobject 193 | $ClientNetworksTested = @() 194 | $NClientResults = @() 195 | $ResultString = "" 196 | $ExpectedTPUTDec = $ExpectedTPUT / 100 197 | 198 | $j = 9000 199 | 200 | $ServerOutput = @() 201 | $ClientOutput = @() 202 | $ServerCounter = @() 203 | $ClientCounter = @() 204 | $ServerSuccess = $True 205 | $MultiClientSuccess = $True 206 | 207 | $ServerCounter = Start-Job -ScriptBlock { 208 | param ([string] $ServerName, [string] $ServerInterfaceDescription) 209 | 210 | Get-Counter -ComputerName $ServerName -Counter "\RDMA Activity($ServerInterfaceDescription)\RDMA Inbound Bytes/sec" -MaxSamples 20 #-ErrorAction Ignore 211 | } -ArgumentList $Server.NodeName,$Server.InterfaceDescription 212 | 213 | $ClientNetwork | ForEach-Object { 214 | $ClientName = $_.NodeName 215 | $ClientIP = $_.IPAddress 216 | $ClientIF = $_.InterfaceIndex 217 | $ClientInterfaceDescription = $_.InterfaceDescription 218 | $ClientLinkSpeedBps = [Int]::Parse($_.LinkSpeed.Split()[0]) * [Math]::Pow(10, 9) / 8 219 | $ServerLinkSpeedBps = [Int]::Parse($Server.LinkSpeed.Split()[0]) * [Math]::Pow(10, 9) / 8 220 | 221 | $ServerOutput += Start-Job -ScriptBlock { 222 | param ([string] $ServerName, [string] $ServerIP, [string] $ServerIF, [int]$j) 223 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 224 | param([string]$ServerIP,[string]$ServerIF,[int]$j) 225 | cmd /c "NdkPerfCmd.exe -S -ServerAddr $($ServerIP):$j -ServerIf $ServerIF -TestType rperf -W 20 2>&1" 226 | } -ArgumentList $ServerIP, $ServerIF, $j 227 | } -ArgumentList $Server.NodeName, $Server.IPAddress, $Server.InterfaceIndex, $j 228 | 229 | $ClientCounter += Start-Job -ScriptBlock { 230 | param ([string] $ClientName, [string] $ClientInterfaceDescription) 231 | 232 | Get-Counter -ComputerName $ClientName -Counter "\RDMA Activity($ClientInterfaceDescription)\RDMA Outbound Bytes/sec" -MaxSamples 20 233 | } -ArgumentList $ClientName,$ClientInterfaceDescription 234 | 235 | $ClientOutput += Start-Job -ScriptBlock { 236 | param ([string] $ClientName, [string] $ServerIP, [string] $ClientIP, [string] $ClientIF, [int]$j) 237 | 238 | Invoke-Command -Computername $ClientName -ScriptBlock { 239 | param ([string] $ServerIP, [string] $ClientIP, [string] $ClientIF, [int] $j) 240 | cmd /c "NdkPerfCmd.exe -C -ServerAddr $($ServerIP):$j -ClientAddr $($ClientIP) -ClientIf $($ClientIF) -TestType rperf 2>&1" 241 | } -ArgumentList $ServerIP,$ClientIP,$ClientIF,$j 242 | } -ArgumentList $ClientName,$Server.IPAddress,$ClientIP,$ClientIF,$j 243 | 244 | $j++ 245 | } 246 | 247 | Sleep 25 248 | 249 | $MinAcceptableLinkSpeedBps = ($ServerLinkSpeedBps, $ClientLinkSpeedBps | Measure-Object -Minimum).Minimum * $ExpectedTPUTDec 250 | 251 | $ServerRecv = Receive-Job $ServerCounter 252 | 253 | $FlatServerRecvOutput = $ServerRecv.Readings.split(":") | ForEach-Object { 254 | try {[uint64]($_)} catch {} 255 | } 256 | 257 | $ServerRecvBps = [Math]::Round(($FlatServerRecvOutput | Measure-Object -Maximum).Maximum, 2) 258 | $ServerSuccess = $ServerRecvBps -gt $MinAcceptableLinkSpeedBps 259 | 260 | $RawData = New-Object -TypeName psobject 261 | $RawData | Add-Member -MemberType NoteProperty -Name ServerBytesPerSecond -Value $ServerRecvBps 262 | $RawData | Add-Member -MemberType NoteProperty -Name MinLinkSpeedBps -Value $MinAcceptableLinkSpeedBps 263 | 264 | $ReceiverLinkSpeedGbps = [Math]::Round(($ServerLinkSpeedBps * 8) * [Math]::Pow(10, -9), 2) 265 | $ServerRecvBitsPerSecond = $ServerRecvBps * 8 266 | $ReceivedGbps = [Math]::Round($ServerRecvBitsPerSecond * [Math]::Pow(10, -9), 2) 267 | $ReceivedPercentageOfLinkSpeed = [Math]::Round(($ReceivedGbps / $ReceiverLinkSpeedGbps) * 100, 2) 268 | 269 | $NDKPerfNto1Results | Add-Member -MemberType NoteProperty -Name ReceiverLinkSpeedGbps -Value $ReceiverLinkSpeedGbps 270 | $NDKPerfNto1Results | Add-Member -MemberType NoteProperty -Name RxGbps -Value $ReceivedGbps 271 | $NDKPerfNto1Results | Add-Member -MemberType NoteProperty -Name ReceivedPctgOfLinkSpeed -Value $ReceivedPercentageOfLinkSpeed 272 | $NDKPerfNto1Results | Add-Member -MemberType NoteProperty -Name ClientNetworkTested -Value $ClientNetwork.IPAddress 273 | $NDKPerfNto1Results | Add-Member -MemberType NoteProperty -Name ServerSuccess -Value $ServerSuccess 274 | $NDKPerfNto1Results | Add-Member -MemberType NoteProperty -Name RawData -Value $RawData 275 | 276 | Return $NDKPerfNto1Results 277 | } 278 | 279 | function Invoke-NDKPerfNtoN { 280 | [CmdletBinding()] 281 | param ( 282 | [Parameter(Mandatory=$true, Position=0)] 283 | [PSObject] $ServerList, 284 | 285 | [Parameter(Mandatory=$true, Position=1)] 286 | [int] $ExpectedTPUT 287 | ) 288 | 289 | $thisSubnet = ($ServerList | Select-Object -First 1).subnet 290 | $thisVLAN = ($ServerList | Select-Object -First 1).VLAN 291 | 292 | $NDKPerfNtoNResults = New-Object -TypeName psobject 293 | $ExpectedTPUTDec = $ExpectedTPUT / 100 294 | 295 | $j = 9000 296 | 297 | $ServerOutput = @() 298 | $ClientOutput = @() 299 | $ServerCounter = @() 300 | $ClientCounter = @() 301 | 302 | $ServerSuccess = $True 303 | 304 | $ServerList | ForEach-Object { 305 | $Server = $_ 306 | $ServerLinkSpeedBps = [Int]::Parse($Server.LinkSpeed.Split()[0]) * [Math]::Pow(10, 9) / 8 307 | $ClientNetwork = $ServerList | Where-Object NodeName -ne $Server.NodeName 308 | 309 | $ServerCounter += Start-Job -ScriptBlock { 310 | param ([string] $ServerName, [string] $ServerInterfaceDescription) 311 | 312 | Get-Counter -ComputerName $ServerName -Counter "\RDMA Activity($ServerInterfaceDescription)\RDMA Inbound Bytes/sec" -MaxSamples 20 313 | } -ArgumentList $Server.NodeName,$Server.InterfaceDescription 314 | 315 | $ClientNetwork | ForEach-Object { 316 | $ClientName = $_.NodeName 317 | $ClientIP = $_.IPAddress 318 | $ClientIF = $_.InterfaceIndex 319 | $ClientInterfaceDescription = $_.InterfaceDescription 320 | $ClientLinkSpeedBps = [Int]::Parse($_.LinkSpeed.Split()[0]) * [Math]::Pow(10, 9) / 8 321 | 322 | $ServerOutput += Start-Job -ScriptBlock { 323 | param ([string] $ServerName, [string] $ServerIP, [string] $ServerIF, [int] $j) 324 | 325 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 326 | param([string]$ServerIP,[string]$ServerIF,[int]$j) 327 | cmd /c "NdkPerfCmd.exe -S -ServerAddr $($ServerIP):$j -ServerIf $ServerIF -TestType rperf -W 20 2>&1" 328 | } -ArgumentList $ServerIP, $ServerIF, $j 329 | } -ArgumentList $Server.NodeName,$Server.IPAddress,$Server.InterfaceIndex,$j 330 | 331 | $ClientCounter += Start-Job ` 332 | -ScriptBlock { 333 | param([string]$ClientName,[string]$ClientInterfaceDescription) 334 | Get-Counter -ComputerName $ClientName -Counter "\RDMA Activity($ClientInterfaceDescription)\RDMA Outbound Bytes/sec" -MaxSamples 20 335 | } -ArgumentList $ClientName,$ClientInterfaceDescription 336 | 337 | $ClientOutput += Start-Job -ScriptBlock { 338 | param ([string] $ClientName, [string] $ServerIP, [string] $ClientIP, [string] $ClientIF, [int]$j) 339 | 340 | Invoke-Command -Computername $ClientName -ScriptBlock { 341 | param ([string] $ServerIP, [string] $ClientIP, [string] $ClientIF, [int]$j) 342 | cmd /c "NdkPerfCmd.exe -C -ServerAddr $($ServerIP):$j -ClientAddr $($ClientIP) -ClientIf $($ClientIF) -TestType rperf 2>&1" 343 | } -ArgumentList $ServerIP,$ClientIP,$ClientIF,$j 344 | } -ArgumentList $ClientName,$Server.IPAddress,$ClientIP,$ClientIF,$j 345 | 346 | $j++ 347 | } 348 | } 349 | 350 | Sleep 40 351 | 352 | $ServerOutput = Receive-Job $ServerOutput 353 | $ServerOutput | ForEach-Object { 354 | $ServerSuccess = $_ -match 'completes' 355 | if ($_) { Write-Host $_ } 356 | } 357 | 358 | $ClientOutput = Receive-Job $ClientOutput 359 | $ClientOutput | ForEach-Object { 360 | if ($_) { Write-Host $_ } 361 | } 362 | 363 | $ServerBytesPerSecond = 0 364 | $ServerBpsArray = @() 365 | $ServerGbpsArray = @() 366 | $MinAcceptableLinkSpeedBps = ($ServerLinkSpeedBps, $ClientLinkSpeedBps | Measure-Object -Minimum).Minimum * $ExpectedTPUTDec 367 | $ServerCounter | ForEach-Object { 368 | 369 | $read = Receive-Job $_ -Wait -AutoRemoveJob 370 | 371 | $FlatServerOutput = $read.Readings.split(":") | ForEach-Object { 372 | try {[uint64]($_)} catch{} 373 | } 374 | $ServerBytesPerSecond = ($FlatServerOutput | Measure-Object -Maximum).Maximum 375 | $ServerBpsArray += $ServerBytesPerSecond 376 | $ServerGbpsArray += [Math]::Round(($ServerBytesPerSecond * 8) * [Math]::Pow(10, -9), 2) 377 | $ServerSuccess = $ServerSuccess -and ($ServerBytesPerSecond -gt $MinAcceptableLinkSpeedBps) 378 | } 379 | 380 | $RawData = New-Object -TypeName psobject 381 | $RawData | Add-Member -MemberType NoteProperty -Name ServerBytesPerSecond -Value $ServerBpsArray 382 | 383 | $NDKPerfNtoNResults | Add-Member -MemberType NoteProperty -Name RxGbps -Value $ServerGbpsArray 384 | $NDKPerfNtoNResults | Add-Member -MemberType NoteProperty -Name ServerSuccess -Value $ServerSuccess 385 | $NDKPerfNtoNResults | Add-Member -MemberType NoteProperty -Name RawData -Value $RawData 386 | 387 | Return $NDKPerfNtoNResults 388 | } 389 | -------------------------------------------------------------------------------- /helpers/prerequisites.psm1: -------------------------------------------------------------------------------- 1 | Function Test-NetStackPrerequisites { 2 | param ( 3 | [String[]] $Nodes, 4 | 5 | [IPAddress[]] $IPTarget, 6 | 7 | [Int32[]] $Stage, 8 | 9 | [Switch] $EnableFirewallRules 10 | ) 11 | 12 | #region Targets 13 | if ($IPTarget) { $Targets = $IPTarget } 14 | else { $Targets = $Nodes } 15 | 16 | $TargetInfo = @() 17 | $Targets | ForEach-Object { 18 | $thisTarget = $_ 19 | 20 | $thisPrereqResult = @{} 21 | $thisPrereqResult = [PSCustomObject] @{ 22 | Name = $thisTarget 23 | } 24 | 25 | $TargetInfo += $thisPrereqResult 26 | } 27 | 28 | $PrereqStatus = @() 29 | #endregion Targets 30 | 31 | #region WinRM and OS 32 | $TargetInfo | Add-Member -MemberType NoteProperty -Name 'WinRM' -Value '' -Force 33 | $TargetInfo | Add-Member -MemberType NoteProperty -Name 'OSVersion' -Value '' -Force 34 | 35 | $Targets | ForEach-Object { 36 | $thisTarget = $_ 37 | 38 | if ($EnableFirewallRules) { 39 | if ($thisTarget -ne $Env:ComputerName) { 40 | $null = Enable-NetFirewallRule -DisplayName 'Windows Remote Management (HTTP-In)' -CimSession $thisTarget -ErrorAction SilentlyContinue 41 | } 42 | else { 43 | $null = Enable-NetFirewallRule -DisplayName 'Windows Remote Management (HTTP-In)' -ErrorAction SilentlyContinue 44 | } 45 | } 46 | 47 | if ($thisTarget -ne $Env:ComputerName) { 48 | # If $NodeOS -ne $null then we can assume WinRM is successful 49 | $NodeOS = Invoke-Command -ComputerName $thisTarget -ErrorAction SilentlyContinue -ScriptBlock { 50 | return $([System.Environment]::OSVersion.Version.Build -ge 20279) 51 | } 52 | 53 | if ($NodeOS) { ($TargetInfo | Where-Object Name -eq $thisTarget).WinRM = $true } 54 | } 55 | else { # Machine is local; no need to test WinRM 56 | $NodeOS = [System.Environment]::OSVersion.Version.Build -ge 20279 57 | ($TargetInfo | Where-Object Name -eq $thisTarget).WinRM = $true 58 | } 59 | 60 | ($TargetInfo | Where-Object Name -eq $thisTarget).OSVersion = $NodeOS 61 | } 62 | 63 | $PrereqStatus += $false -notin $TargetInfo.WinRM 64 | $PrereqStatus += $false -notin $TargetInfo.OSVersion 65 | #endregion WinRM and OS 66 | 67 | Switch ( $Stage | Sort-Object ) { 68 | 1 { 69 | $TargetInfo | Add-Member -MemberType NoteProperty -Name 'ICMP' -Value '' -Force 70 | 71 | $Targets | ForEach-Object { 72 | if ($EnableFirewallRules) { 73 | if ($thisTarget -ne $Env:ComputerName) { 74 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In' -CimSession $thisTarget -ErrorAction SilentlyContinue 75 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In' -CimSession $thisTarget -ErrorAction SilentlyContinue 76 | 77 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In-NoScope' -CimSession $thisTarget -ErrorAction SilentlyContinue 78 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In-NoScope' -CimSession $thisTarget -ErrorAction SilentlyContinue 79 | } 80 | else { 81 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In' -ErrorAction SilentlyContinue 82 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In' -ErrorAction SilentlyContinue 83 | 84 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In-NoScope' -ErrorAction SilentlyContinue 85 | $null = Enable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In-NoScope' -ErrorAction SilentlyContinue 86 | } 87 | } 88 | 89 | $thisTarget = $_ 90 | 91 | if ($thisTarget -ne $Env:ComputerName) { 92 | $ICMPResult = Test-NetConnection -ComputerName $thisTarget -InformationLevel Quiet 93 | ($TargetInfo | Where-Object Name -eq $thisTarget).ICMP = $ICMPResult 94 | } 95 | else { # Machine is local; no need to test 96 | ($TargetInfo | Where-Object Name -eq $thisTarget).ICMP = $true 97 | } 98 | 99 | $PrereqStatus += $false -notin $TargetInfo.ICMP 100 | } 101 | } 102 | 103 | 2 { 104 | $TargetInfo | Add-Member -MemberType NoteProperty -Name 'Module' -Value '' -Force 105 | $TargetInfo | Add-Member -MemberType NoteProperty -Name 'Version' -Value '' -Force 106 | 107 | $Targets | ForEach-Object { 108 | $thisTarget = $_ 109 | 110 | if ($thisTarget -ne $Env:ComputerName) { 111 | $Module = Invoke-Command -ComputerName $thisTarget -ScriptBlock { 112 | Get-Module Test-NetStack -ListAvailable -ErrorAction SilentlyContinue | Select-Object -First 1 113 | } 114 | 115 | if ($Module) { 116 | ($TargetInfo | Where-Object Name -eq $thisTarget).Module = $true 117 | ($TargetInfo | Where-Object Name -eq $thisTarget).Version = $Module.Version 118 | } 119 | else { ($TargetInfo | Where-Object Name -eq $thisTarget).Module = $false } 120 | 121 | if ($EnableFirewallRules) { 122 | $psSession = New-PSSession -ComputerName $thisTarget 123 | $ModuleBase = (Get-Module Test-NetStack -PSSession $psSession -ListAvailable).ModuleBase 124 | Remove-PSSession -Session $psSession 125 | $null = New-NetFirewallRule -CimSession $thisTarget -DisplayName 'Test-NetStack - NTTTCP' -Direction Inbound -Program "$ModuleBase\tools\NTttcp\ntttcp.exe" -Action Allow -ErrorAction SilentlyContinue 126 | } 127 | } 128 | else { # Machine is local; no need to test 129 | $Module = Get-Module Test-NetStack -ListAvailable -ErrorAction SilentlyContinue | Select-Object -First 1 130 | 131 | if ($Module) { 132 | ($TargetInfo | Where-Object Name -eq $thisTarget).Module = $true 133 | ($TargetInfo | Where-Object Name -eq $thisTarget).Version = $Module.Version 134 | } 135 | else { ($TargetInfo | Where-Object Name -eq $thisTarget).Module = $false } 136 | 137 | if ($EnableFirewallRules) { 138 | $ModuleBase = (Get-Module Test-NetStack -ListAvailable | Select-Object -First 1).ModuleBase 139 | $null = New-NetFirewallRule -DisplayName 'Test-NetStack - NTTTCP' -Direction Inbound -Program "$ModuleBase\tools\NTttcp\ntttcp.exe" -Action Allow -ErrorAction SilentlyContinue 140 | } 141 | } 142 | } 143 | 144 | $PrereqStatus += $false -notin $TargetInfo.Module 145 | $PrereqStatus += ($TargetInfo.Version | Select-Object -Unique).Count -eq 1 146 | } 147 | 148 | { $_ -eq 3 -or $_ -eq 4 -or $_ -eq 5 -or $_ -eq 6 } { 149 | $Targets | ForEach-Object { 150 | $thisTarget = $_ 151 | 152 | if ($thisTarget -ne $Env:ComputerName) { 153 | if ($EnableFirewallRules) { 154 | $null = Enable-NetFirewallRule 'FPSSMBD-iWARP-In-TCP' -CimSession $thisTarget -ErrorAction SilentlyContinue 155 | } 156 | } 157 | else { # Machine is local; no need to test 158 | if ($EnableFirewallRules) { 159 | $null = Enable-NetFirewallRule 'FPSSMBD-iWARP-In-TCP' -ErrorAction SilentlyContinue 160 | } 161 | } 162 | } 163 | } 164 | 165 | 4 { } 166 | 5 { } 167 | 6 { } 168 | } 169 | 170 | return $TargetInfo, $PrereqStatus 171 | } 172 | 173 | Function Revoke-FirewallRules { 174 | param ( 175 | $Targets, 176 | [Int32[]] $Stage 177 | ) 178 | 179 | Write-Warning 'WinRM rules with DisplayName "Windows Remote Management (HTTP-In)" will not be disabled' 180 | 181 | Switch ( $Stage | Sort-Object ) { 182 | 1 { 183 | $Targets | ForEach-Object { 184 | $thisTarget = $_ 185 | 186 | if ($thisTarget -ne $Env:ComputerName) { 187 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In' -CimSession $thisTarget -ErrorAction SilentlyContinue 188 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In' -CimSession $thisTarget -ErrorAction SilentlyContinue 189 | 190 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In-NoScope' -CimSession $thisTarget -ErrorAction SilentlyContinue 191 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In-NoScope' -CimSession $thisTarget -ErrorAction SilentlyContinue 192 | } 193 | else { 194 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In' -ErrorAction SilentlyContinue 195 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In' -ErrorAction SilentlyContinue 196 | 197 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP4-ERQ-In-NoScope' -ErrorAction SilentlyContinue 198 | $null = Disable-NetFirewallRule -Name 'FPS-ICMP6-ERQ-In-NoScope' -ErrorAction SilentlyContinue 199 | } 200 | } 201 | } 202 | 203 | 2 { 204 | $Targets | ForEach-Object { 205 | $thisTarget = $_ 206 | 207 | if ($thisTarget -ne $Env:ComputerName) { 208 | $null = Remove-NetFirewallRule -DisplayName 'Test-NetStack - NTTTCP' -CimSession $thisTarget -ErrorAction SilentlyContinue 209 | } 210 | else { 211 | $null = Remove-NetFirewallRule -DisplayName 'Test-NetStack - NTTTCP' -ErrorAction SilentlyContinue 212 | } 213 | } 214 | } 215 | 216 | { $_ -eq 3 -or $_ -eq 4 -or $_ -eq 5 -or $_ -eq 6 } { 217 | $Targets | ForEach-Object { 218 | $thisTarget = $_ 219 | 220 | if ($thisTarget -ne $Env:ComputerName) { 221 | $null = Disable-NetFirewallRule -Name 'FPSSMBD-iWARP-In-TCP' -CimSession $thisTarget -ErrorAction SilentlyContinue 222 | } 223 | else { 224 | $null = Disable-NetFirewallRule -Name 'FPSSMBD-iWARP-In-TCP' -ErrorAction SilentlyContinue 225 | } 226 | } 227 | } 228 | 229 | 3 { } 230 | 4 { } 231 | 5 { } 232 | 6 { } 233 | } 234 | } -------------------------------------------------------------------------------- /helpers/tcp.psm1: -------------------------------------------------------------------------------- 1 | function Invoke-TCP { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory=$true, Position=0)] 5 | [PSObject] $Receiver, 6 | 7 | [Parameter(Mandatory=$true, Position=1)] 8 | [PSObject] $Sender, 9 | 10 | [Parameter(Mandatory=$true, Position=2)] 11 | $LogDir 12 | ) 13 | 14 | $ModuleBase = (Get-Module Test-NetStack -ListAvailable | Select-Object -First 1).ModuleBase 15 | 16 | # CTS Traffic Rate Limit is specified in bytes/second 17 | $ServerLinkSpeed = $Receiver.LinkSpeed.split(" ") 18 | Switch($ServerLinkSpeed[1]) { 19 | ("Gbps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) * [Math]::Pow(10, 9) / 8} 20 | ("Mbps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) * [Math]::Pow(10, 6) / 8} 21 | ("Kbps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) * [Math]::Pow(10, 3) / 8} 22 | ("bps") {$ServerLinkSpeedBps = [Int]::Parse($ServerLinkSpeed[0]) / 8} 23 | } 24 | 25 | $ClientLinkSpeed = $Sender.LinkSpeed.split(" ") 26 | Switch($ClientLinkSpeed[1]) { 27 | ("Gbps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) * [Math]::Pow(10, 9) / 8} 28 | ("Mbps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) * [Math]::Pow(10, 6) / 8} 29 | ("Kbps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) * [Math]::Pow(10, 3) / 8} 30 | ("bps") {$ClientLinkSpeedBps = [Int]::Parse($ClientLinkSpeed[0]) / 8} 31 | } 32 | 33 | $DefaultBufferSize = 65536 34 | $BufferSize = $DefaultBufferSize * $ServerLinkSpeed[0] 35 | 36 | $ServerOutput = Start-Job -ScriptBlock { 37 | param ([string] $ServerName, [string] $ServerIP, $ModuleBase, $LogDir, $BufferSize) 38 | 39 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 40 | param ([string] $ServerIP, [string] $ModuleBase, $LogDir, $BufferSize) 41 | Set-Location $ModuleBase 42 | cmd /c ".\tools\NTttcp\ntttcp.exe -r -m 64,*,$ServerIP -l $BufferSize -a 16 -v -t 20" | Out-File "$($LogDir)\NTttcp_$($ServerIP)_Recv_$(Get-Date -f yyyy-MM-dd-HHmmss).txt" -Append 43 | } -ArgumentList $ServerIP, $ModuleBase, $LogDir, $BufferSize 44 | 45 | } -ArgumentList $Receiver.NodeName, $Receiver.IPAddress, $ModuleBase, $LogDir, $BufferSize 46 | 47 | $ServerRecvCounter = Start-Job -ScriptBlock { 48 | param ([string] $ServerName, [string] $ServerInterfaceDescription) 49 | $ServerInterfaceDescription = (((($ServerInterfaceDescription) -replace '#', '_') -replace '[(]', '[') -replace '[)]', ']') -replace '/', '_' 50 | 51 | Invoke-Command -ComputerName $ServerName -ScriptBlock { 52 | param([string]$ServerInterfaceDescription) 53 | Get-Counter -Counter "\Network Adapter($ServerInterfaceDescription)\Bytes Received/sec" -MaxSamples 20 -ErrorAction Ignore 54 | } -ArgumentList $ServerInterfaceDescription 55 | 56 | } -ArgumentList $Receiver.NodeName, $Receiver.InterfaceDescription 57 | 58 | $ClientSendCounter = Start-Job -ScriptBlock { 59 | param([string] $ClientName, [string] $ClientInterfaceDescription) 60 | $ClientInterfaceDescription = (((($ClientInterfaceDescription) -replace '#', '_') -replace '[(]', '[') -replace '[)]', ']') -replace '/', '_' 61 | 62 | Invoke-Command -ComputerName $ClientName -ScriptBlock { 63 | param ([string] $ClientInterfaceDescription) 64 | Get-Counter -Counter "\Network Adapter($ClientInterfaceDescription)\Bytes Sent/sec" -MaxSamples 20 65 | } -ArgumentList $ClientInterfaceDescription 66 | 67 | } -ArgumentList $Sender.NodeName,$Sender.InterfaceDescription 68 | 69 | $ClientOutput = Start-Job -ScriptBlock { 70 | param ([string] $ClientName, [string] $ServerIP, [string] $ClientIP, [string] $ModuleBase, $LogDir, $BufferSize) 71 | 72 | Invoke-Command -ComputerName $ClientName -ScriptBlock { 73 | param ([string] $ServerIP, [string] $ClientIP, $ModuleBase, $LogDir, $BufferSize) 74 | Set-Location $ModuleBase 75 | cmd /c ".\tools\NTttcp\ntttcp.exe -s -m 64,*,$ServerIP -l $BufferSize -a 16 -v -t 20" | Out-File "$($LogDir)\NTttcp_$($clientip)_Send_$(Get-Date -f yyyy-MM-dd-HHmmss).txt" -Append 76 | } -ArgumentList $ServerIP, $ClientIP, $ModuleBase, $LogDir, $BufferSize 77 | 78 | } -ArgumentList $Sender.NodeName, $Receiver.IPAddress, $Sender.IPAddress, $ModuleBase, $LogDir, $BufferSize 79 | 80 | Sleep 20 81 | 82 | $ServerRecv = Receive-Job $ServerRecvCounter 83 | $ClientSend = Receive-Job $ClientSendCounter 84 | 85 | $FlatServerRecvOutput = $ServerRecv.Readings.split(":") | ForEach-Object { 86 | try {[uint64]($_) * 8} catch {} 87 | } 88 | $FlatClientSendOutput = $ClientSend.Readings.split(":") | ForEach-Object { 89 | try {[uint64]($_) * 8} catch {} 90 | } 91 | 92 | $ServerRecvBitsPerSecond = [Math]::Round(($FlatServerRecvOutput | Measure-Object -Maximum).Maximum, 2) 93 | $ClientSendBitsPerSecond = [Math]::Round(($FlatClientSendOutput | Measure-Object -Maximum).Maximum, 2) 94 | 95 | Write-Verbose "Server Recv bps: $ServerRecvBitsPerSecond" 96 | Write-Verbose "Client Send bps: $ClientSendBitsPerSecond" 97 | 98 | $ServerOutput = Receive-Job $ServerOutput 99 | $ClientOutput = Receive-Job $ClientOutput 100 | 101 | $ServerLinkSpeedBitsPerSecond = $ServerLinkSpeedBps * 8 102 | $ClientLinkSpeedBitsPerSecond = $ClientLinkSpeedBps * 8 103 | 104 | $MinLinkSpeedBitsPerSecond = ($ServerLinkSpeedBitsPerSecond, $ClientLinkSpeedBitsPerSecond | Measure-Object -Minimum).Minimum 105 | Write-Verbose "Minimum Link Speed bps: $MinLinkSpeedBitsPerSecond" 106 | 107 | $RawData = New-Object -TypeName psobject 108 | $RawData | Add-Member -MemberType NoteProperty -Name ServerRxbps -Value $ServerRecvBitsPerSecond 109 | $RawData | Add-Member -MemberType NoteProperty -Name ClientTxbps -Value $ClientSendBitsPerSecond 110 | $RawData | Add-Member -MemberType NoteProperty -Name MinLinkSpeedbps -Value $MinLinkSpeedBitsPerSecond 111 | 112 | $ReceiverLinkSpeedGbps = [Math]::Round($ServerLinkSpeedBitsPerSecond * [Math]::Pow(10, -9), 2) 113 | $ReceivedGbps = [Math]::Round($ServerRecvBitsPerSecond * [Math]::Pow(10, -9), 2) 114 | $ReceivedPercentageOfLinkSpeed = [Math]::Round(($ReceivedGbps / $ReceiverLinkSpeedGbps) * 100, 2) 115 | 116 | $TCPResults = New-Object -TypeName psobject 117 | $TCPResults | Add-Member -MemberType NoteProperty -Name ReceiverLinkSpeedGbps -Value $ReceiverLinkSpeedGbps 118 | $TCPResults | Add-Member -MemberType NoteProperty -Name ReceivedGbps -Value $ReceivedGbps 119 | $TCPResults | Add-Member -MemberType NoteProperty -Name ReceivedPctgOfLinkSpeed -Value $ReceivedPercentageOfLinkSpeed 120 | $TCPResults | Add-Member -MemberType NoteProperty -Name RawData -Value $RawData 121 | 122 | Return $TCPResults 123 | } -------------------------------------------------------------------------------- /tests/results/TestResults.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/setup/deploy.ps1: -------------------------------------------------------------------------------- 1 | git config --global credential.helper store 2 | 3 | Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:GitHubKey):x-oauth-basic@https://github.com`n" 4 | 5 | git config --global user.email "dcuomo@outlook.com" 6 | git config --global user.name "Dan Cuomo" 7 | git config --global core.autocrlf false 8 | git config --global core.safecrlf false 9 | 10 | # Line break for readability in AppVeyor console 11 | Write-Host -Object '' 12 | 13 | # Make sure we're using the main branch and that it's not a pull request 14 | # Environmental Variables Guide: https://www.appveyor.com/docs/environment-variables/ 15 | if ($env:APPVEYOR_REPO_BRANCH -ne 'main') 16 | { 17 | Write-Warning -Message "Skipping version increment and publish for branch $env:APPVEYOR_REPO_BRANCH" 18 | } 19 | elseif ($env:APPVEYOR_PULL_REQUEST_NUMBER -gt 0) 20 | { 21 | Write-Warning -Message "Skipping version increment and publish for pull request #$env:APPVEYOR_PULL_REQUEST_NUMBER" 22 | } 23 | else 24 | { 25 | # We're going to add 1 to the revision value since a new commit has been merged to main 26 | # This means that the major / minor / build values will be consistent across GitHub and the Gallery 27 | Try 28 | { 29 | # This is where the module manifest lives 30 | $manifestPath = ".\$($env:RepoName).psd1" 31 | 32 | # Start by importing the manifest to determine the version, then add 1 to the revision 33 | $manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction SilentlyContinue 34 | [System.Version]$version = $manifest.Version 35 | Write-Output "Old Version: $version" 36 | [String]$newVersion = "$(Get-Date -format yyyy.MM.dd).$env:appveyor_build_number" 37 | Write-Output "New Version: $newVersion" 38 | 39 | # Update the manifest with the new version value and fix the weird string replace bug 40 | #$functionList = ((Get-ChildItem -Path .\$($env:RepoName)).BaseName) 41 | $splat = @{ 42 | 'Path' = $manifestPath 43 | 'ModuleVersion' = $newVersion 44 | #'FunctionsToExport' = $functionList 45 | 'Copyright' = "(c) $( (Get-Date).Year ) Inc. All rights reserved." 46 | } 47 | 48 | Update-ModuleManifest @splat -ErrorAction SilentlyContinue 49 | (Get-Content -Path $manifestPath) -replace "PSGet_$($env:RepoName)", "$($env:RepoName)" | Set-Content -Path $manifestPath 50 | (Get-Content -Path $manifestPath) -replace 'NewManifest', "$($env:RepoName)" | Set-Content -Path $manifestPath 51 | #(Get-Content -Path $manifestPath) -replace 'FunctionsToExport = ', 'FunctionsToExport = @(' | Set-Content -Path $manifestPath -Force 52 | #(Get-Content -Path $manifestPath) -replace "$($functionList[-1])'", "$($functionList[-1])')" | Set-Content -Path $manifestPath -Force 53 | } 54 | catch 55 | { 56 | throw $_ 57 | } 58 | 59 | # Publish the new version to the PowerShell Gallery 60 | Try 61 | { 62 | # Build a splat containing the required details and make sure to Stop for errors which will trigger the catch 63 | $PM = @{ 64 | Path = '.' 65 | NuGetApiKey = $env:NuGetApiKey 66 | ErrorAction = 'Stop' 67 | Force = $true 68 | } 69 | 70 | Publish-Module @PM 71 | Write-Host "$($env:RepoName) PowerShell Module version $newVersion published to the PowerShell Gallery." -ForegroundColor Cyan 72 | } 73 | Catch 74 | { 75 | Write-Warning "Publishing update $newVersion to the PowerShell Gallery failed." 76 | throw $_ 77 | } 78 | 79 | # Publish the new version back to main on GitHub 80 | Try 81 | { 82 | # Set up a path to the git.exe cmd, import posh-git to give us control over git, and then push changes to GitHub 83 | # Note that "update version" is included in the appveyor.yml file's "skip a build" regex to avoid a loop 84 | $env:Path += ";$env:ProgramFiles\Git\cmd" 85 | Import-Module posh-git -ErrorAction Stop 86 | git checkout main -q 87 | git add --all 88 | git status 89 | git commit -s -m "Update version to $newVersion" 90 | git branch -u origin/main 91 | git push origin main -q 92 | Write-Host "$($env:RepoName) PowerShell Module version $newVersion published to GitHub." -ForegroundColor Cyan 93 | } 94 | Catch 95 | { 96 | Write-Warning "Publishing update $newVersion to GitHub failed." 97 | throw $_ 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/setup/initiate-tests.ps1: -------------------------------------------------------------------------------- 1 | # Invoke Pester to run tests, then save the results in NUnitXML to populate the AppVeyor tests section 2 | # Pester : https://github.com/pester/Pester/wiki 3 | # Pester Code Coverage : https://info.sapien.com/index.php/scripting/scripting-modules/testing-pester-code-coverage 4 | 5 | Import-Module Pester -RequiredVersion '4.9.0' 6 | New-Item -Path .\tests -Name results -ItemType Directory -Force | Out-Null 7 | 8 | $testResultPath = '.\tests\results\TestResults.xml' 9 | # This is a manifest so no code coverage is possible. Original line kept below: 10 | #...\results\TestsResults.xml -PassThru -CodeCoverage .\MSFT.Network.Tools.psd1 11 | $res = Invoke-Pester -Path ".\tests\unit" -OutputFormat NUnitXml -OutputFile $testResultPath -PassThru 12 | 13 | (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultPath)) 14 | 15 | if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed." } 16 | -------------------------------------------------------------------------------- /tests/setup/install.ps1: -------------------------------------------------------------------------------- 1 | git.exe clone -q https://github.com/PowerShell/DscResource.Tests 2 | 3 | Import-Module -Name "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1" 4 | Invoke-AppveyorInstallTask 5 | 6 | Remove-Item .\DscResource.Tests\ -Force -Confirm:$false -Recurse 7 | 8 | # To be replaced with checking version and downloading new NTttcp exe as needed 9 | <# 10 | Write-Output 'Checking version of CTSTraffic...' 11 | 12 | git.exe clone -q https://github.com/microsoft/ctsTraffic c:\projects\CTSTraffic 13 | $Releases = (Get-ChildItem c:\projects\CTSTraffic\Releases).Name | Sort-Object -Descending | Select-Object -First 1 14 | 15 | $ExistingVersion = (Get-ItemProperty 'C:\projects\Test-NetStack\tools\CTS-Traffic\ctstraffic.exe').versioninfo.fileversion 16 | 17 | if ($ExistingVersion -ne $Releases) { 18 | Write-Output "Updating CTSTraffic from $ExistingVersion to $Releases" 19 | 20 | Copy-Item "C:\projects\CTSTraffic\Releases\$Releases\x64\ctstraffic.exe" 'C:\projects\Test-NetStack\tools\CTS-Traffic\ctstraffic.exe' -force 21 | 22 | Write-Output "Updated CTSTraffic from $ExistingVersion to $Releases" 23 | } 24 | else { 25 | Write-Output "Existing CTSTraffic version ($ExistingVersion) matches the current CTSTraffic version ($Releases)" 26 | } 27 | 28 | Remove-Item C:\projects\CTSTraffic -Force -Confirm:$false -Recurse 29 | 30 | Write-Output '...Ending CTSTraffic version check' 31 | #> 32 | 33 | [string[]]$PowerShellModules = @("Pester", 'posh-git') 34 | 35 | $ModuleManifest = Test-ModuleManifest .\$($env:RepoName).psd1 -ErrorAction SilentlyContinue 36 | $repoRequiredModules = $ModuleManifest.RequiredModules.Name 37 | $repoRequiredModules += $ModuleManifest.PrivateData.PSData.ExternalModuleDependencies 38 | 39 | If ($repoRequiredModules) { $PowerShellModules += $repoRequiredModules } 40 | 41 | # Feature Installation 42 | $serverFeatureList = @('Hyper-V') 43 | 44 | If ($PowerShellModules -contains 'FailoverClusters') { 45 | $serverFeatureList += 'RSAT-Clustering-Mgmt', 'RSAT-Clustering-PowerShell' 46 | } 47 | 48 | $BuildSystem = Get-CimInstance -ClassName 'Win32_OperatingSystem' 49 | 50 | ForEach ($Module in $PowerShellModules) { 51 | If ($Module -eq 'FailoverClusters') { 52 | Switch -Wildcard ($BuildSystem.Caption) { 53 | '*Windows 10*' { 54 | Write-Output 'Build System is Windows 10' 55 | Write-Output "Not Implemented" 56 | 57 | # Get FailoverCluster Capability Name and Install on W10 Builds 58 | $capabilityName = (Get-WindowsCapability -Online | Where-Object Name -like *RSAT*FailoverCluster.Management*).Name 59 | Add-WindowsCapability -Name $capabilityName -Online 60 | } 61 | 62 | Default { 63 | Write-Output "Build System is $($BuildSystem.Caption)" 64 | Install-WindowsFeature -Name $serverFeatureList -IncludeManagementTools | Out-Null 65 | } 66 | } 67 | } 68 | ElseIf ($Module -eq 'Pester') { 69 | Write-Output "Installing Pester version 4.9.0" 70 | Install-Module $Module -Scope AllUsers -Force -Repository PSGallery -AllowClobber -SkipPublisherCheck -RequiredVersion 4.9.0 71 | Import-Module $Module -RequiredVersion 4.9.0 72 | } 73 | else { 74 | Install-Module $Module -Scope AllUsers -Force -Repository PSGallery -AllowClobber 75 | Import-Module $Module 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/unit/unit.tests.ps1: -------------------------------------------------------------------------------- 1 | $DataFile = Import-PowerShellDataFile .\$($env:repoName).psd1 -ErrorAction SilentlyContinue 2 | $TestModule = Test-ModuleManifest .\$($env:repoName).psd1 -ErrorAction SilentlyContinue 3 | 4 | Describe "$($env:APPVEYOR_BUILD_FOLDER)-Manifest" { 5 | Context Validation { 6 | It "[Manifest] - $($env:repoName).psd1 exists" { Test-Path "$($env:repoName).psd1" | Should Be True } 7 | 8 | It "[Test-Path] - $($env:repoName).psm1 exists" { Test-Path "$($env:repoName).psm1" | Should Be True } 9 | 10 | It "[Manifest Property] - $($env:repoName).psm1 exists" { $DataFile.RootModule | Should Be "$($env:repoName).psm1" } 11 | 12 | It "[Import-PowerShellDataFile] - $($env:repoName).psd1 is a valid PowerShell Data File" { 13 | $DataFile | Should Not BeNullOrEmpty 14 | } 15 | 16 | It "[Test-ModuleManifest] - $($env:repoName).psd1 should pass the basic test" { 17 | $TestModule | Should Not BeNullOrEmpty 18 | } 19 | 20 | 'icmp.psm1', 'internal.psm1', 'ndk.psm1', 'tcp.psm1' | ForEach-Object { 21 | $thisModule = $_ 22 | 23 | It "[Test-Path] - helpers\$thisModule exists" { Test-Path ".\helpers\$thisModule" | Should Be True } 24 | 25 | Import-Module .\helpers\$thisModule -Force 26 | $Module = Get-Module $thisModule.Split('.')[0] 27 | 28 | It "[Import-Module] - helpers\$thisModule is a valid PowerShell Module" { 29 | $Module | Should Not BeNullOrEmpty 30 | } 31 | 32 | Switch ($Module.Name) { 33 | 'icmp' { 34 | 'Invoke-ICMPPMTUD' | ForEach-Object { 35 | It "Should have an available command: $_" { 36 | $module.ExportedCommands.ContainsKey($_) | Should be $true 37 | } 38 | } 39 | } 40 | 41 | 'internal' { 42 | 'Get-ConnectivityMapping', 'Get-TestableNetworksFromMapping', 'Get-DisqualifiedNetworksFromMapping', 'Get-RunspaceGroups', 43 | 'Get-Jitter', 'Get-Latency', 'Get-Failures', 'Write-RecommendationsToLogFile', 'Convert-CIDRToMask', 'Convert-MaskToCIDR', 'Convert-IPv4ToInt', 44 | 'Convert-IntToIPv4' | ForEach-Object { 45 | It "Should have an available command: $_" { 46 | $module.ExportedCommands.ContainsKey($_) | Should be $true 47 | } 48 | } 49 | 50 | <# This currently does not properly import classes so we won't test this at this time 51 | $Analyzer = [Analyzer]::New() 52 | 53 | It "Should have a class named $thisClass" { 54 | $Analyzer | Should Not BeNullOrEmpty 55 | } 56 | 57 | 'MTU', 'Reliability', 'TCPPerf', 'NDKPerf' | ForEach-Object { 58 | $thisClass = $_ 59 | 60 | It "Analyzer should define the class named $thisClass" { 61 | $Analyzer.$thisClass | Should Not BeNullOrEmpty 62 | } 63 | 64 | Switch ($thisClass) { 65 | 'Reliability' { 66 | It "Should require ICMPReliability to be -ge 90" { 67 | $Analyzer.$thisClass.ICMPReliability | Should BeGreaterOrEqual 90 68 | } 69 | 70 | It "Should require ICMPPacketLoss to be -ge 95" { 71 | $Analyzer.$thisClass.ICMPReliability | Should BeGreaterOrEqual 95 72 | } 73 | } 74 | 75 | 'TCPPerf' { 76 | It "Should require TCP TPUT to be -ge 90" { 77 | $Analyzer.$thisClass.TPUT | Should BeGreaterOrEqual 90 78 | } 79 | } 80 | 81 | 'NDKPerf' { 82 | It "Should require NDK TPUT to be -ge 90" { 83 | $Analyzer.$thisClass.TPUT | Should BeGreaterOrEqual 90 84 | } 85 | } 86 | } 87 | } 88 | #> 89 | } 90 | 91 | 'ndk' { 92 | 'Invoke-NDKPing', 'Invoke-NDKPerf1to1', 'Invoke-NDKPerfNto1', 'Invoke-NDKPerfNtoN' | ForEach-Object { 93 | It "Should have an available command: $_" { 94 | $module.ExportedCommands.ContainsKey($_) | Should be $true 95 | } 96 | } 97 | } 98 | 99 | 'tcp' { 100 | 'Invoke-TCP' | ForEach-Object { 101 | It "Should have an available command: $_" { 102 | $module.ExportedCommands.ContainsKey($_) | Should be $true 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | <# 110 | 'Test-NICAdvancedProperties', 'Test-SwitchCapability' | ForEach-Object { 111 | It "Should have an available command: $_" { 112 | $module.ExportedCommands.ContainsKey($_) | Should be $true 113 | } 114 | } 115 | 116 | It "Should have an available alias: Test-NICProperties" { 117 | $module.ExportedAliases.ContainsKey('Test-NICProperties') | Should be $true 118 | } 119 | 120 | It "Should have an reference command: Test-NICAdvancedProperties" { 121 | $module.ExportedAliases.'Test-NICProperties'.ReferencedCommand.Name | Should be 'Test-NICAdvancedProperties' 122 | } 123 | 124 | It "Should have an required module of: DataCenterBridging" { 125 | $module.RequiredModules | Should be 'DataCenterBridging' 126 | } 127 | #> 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tools/CTS-Traffic/ctsTraffic.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Test-NetStack/f840d723ea84c151f7e3213e2e1725bd9304fe2e/tools/CTS-Traffic/ctsTraffic.exe -------------------------------------------------------------------------------- /tools/NTttcp/NTttcp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Test-NetStack/f840d723ea84c151f7e3213e2e1725bd9304fe2e/tools/NTttcp/NTttcp.exe --------------------------------------------------------------------------------