TurnKeySdn.psm1

<#
.SYNOPSIS
Deploys a SDN environment with minimal user input.
.DESCRIPTION
For more details see TurnKeySdn.md
#>


$Global:ErrorActionPreference = 'Stop'

function Show-TurnKeySdnConfig {
    <#
    .Synopsis
    Show the current deployment configuration.
    #>

    Initialize-StorePath | Out-Null

    $configs = @()
    $configs += Get-DeploymentConfig
    $configs += Get-DefaultMacPoolConfig
    $configs += Get-MgmtNetworkConfig
    $configs += Get-HnvPaNetworkConfig
    return $configs
}

function Set-TurnKeySdnCredentials {
    <#
    .SYNOPSIS
    Sets the credentials for the SDN deployment.
    .DESCRIPTION
    The credential is used to connect to the Hyper-V hosts, domain join VMs and deploy the SDN environment.
    .PARAMETER Credential
    The credential to use for the deployment.
    #>

    param (
        [parameter(Mandatory = $true)][pscredential] $credential,
        [switch]$DoNotPersist
    )

    Initialize-StorePath | Out-Null

    if ($DoNotPersist)
    {
        $Global:SdnCredential = $Credential
        return
    }

    $cred = @{}
    $cred["username"] = $credential.UserName
    $cred["password"] = $credential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
    Set-TurnKeySdnInternalConfig -configType credential -config $cred
}

function Set-TurnKeySdnCorpCredentials {
    <#
    .SYNOPSIS
    Sets the credentials for accessing MSFT corp resources.
    .DESCRIPTION
    The credential is used to access MSFT corp resources like the windows build share, private package share etc.
    .PARAMETER Credential
    The credential.
    #>


    param (
        [parameter(Mandatory = $true)][pscredential] $Credential
    )

    $Global:CorpCredential = $Credential
}

function Set-TurnKeySdnWorkloadImage {
    <#
    .SYNOPSIS
    Set the image to use for the workload VMs.
    .DESCRIPTION
    This image is only used for deploying test workloads. The image is not used for deploying the SDN environment.
    See Set-TurnKeySdnDeploymentVhd for setting the image for the SDN environment.
    .PARAMETER os
    The OS type. Valid values are Linux, Windows.
    .PARAMETER osSku
    The OS SKU. Valid values are Mariner, WindowsLatest.
    .PARAMETER vhdPath
    The directory where the VHD/VHDx is located.
    .PARAMETER vhdFile
    The VHD/VHDx file name.
    #>


    param(
        [ValidateSet("Linux", "Windows")]
        [parameter(Mandatory = $true)][string] $os,
        # WindowsLatest is to download the latest build (internal)
        # Default is to pick up the default VHD available (the one used for SF NC)
        [ValidateSet("Mariner", "WindowsLatest")]
        [parameter(Mandatory = $true)][string] $osSku,
        [parameter(Mandatory = $true)][string] $vhdPath,
        [parameter(Mandatory = $true)][string] $vhdFile
    )

    Initialize-StorePath | Out-Null

    $vhd = Join-Path $vhdPath $vhdFile
    Test-FileExist -path $vhd

    $workloadConfig = Get-TurnKeySdnWorkloadConfig
    $updated = $false
    $workloadConfig.imageconfigs | ForEach-Object {
        $imageConfig = $_
        if ($imageConfig.osType -eq $os -and $imageConfig.osSku -eq $osSku) {
            $imageConfig.vhdPath = $vhdPath
            $imageConfig.vhdFile = $vhdFile
            $updated = $true
            return
        }
    }

    if (-not $updated) {
        $newImage = @{}
        $newImage["osType"] = $os
        $newImage["osSku"] = $osSku
        $newImage["vhdPath"] = $vhdPath
        $newImage["vhdFile"] = $vhdFile
        $workloadConfig.imageconfigs += $newImage
    }

    Set-TurnKeySdnWorkloadConfig -config $workloadConfig
}

function Set-TurnKeySdnRestName {
    
    <#
    .SYNOPSIS
    Sets a custom REST endpoint name for the SDN deployment.
    .DESCRIPTION
    If a custom REST endpoint name is not specified, a random name is generated.
    .PARAMETER RestName
    The custom REST endpoint name.
    #>


    param(
        [parameter(Mandatory = $true)][string]$restName
    )

    Initialize-StorePath | Out-Null

    $sdnConfig = Get-SdnConfig
    $sdnConfig.networkController.restName = $restName
    Set-TurnKeySdnConfig -sdnConfig $sdnConfig
}

function Set-TurnKeySdnDeploymentVhd {
  
    <#
    .SYNOPSIS
    Sets the VHD/VHDx to use for the SDN deployment.
    .DESCRIPTION
    This VHD/VHDx is only used for deploying the SDN environment.
    See Set-TurnKeySdnWorkloadImage for setting the image for the workload VMs.
    .PARAMETER vhdPath
    The directory where the VHD/VHDx is located.
    .PARAMETER vhdFile
    The VHD/VHDx file name.
    .PARAMETER setDefaultForWorkload
    Sets the VHD/VHDx as the default image for the workload VMs.
    #>


    param(
        [parameter(Mandatory = $true)][string] $vhdPath,
        [parameter(Mandatory = $true)][string] $vhdFile,
        [parameter(Mandatory = $false)][switch]$setDefaultForWorkload
    )


    Initialize-StorePath | Out-Null

    $vhdFile = $vhdFile.Split("\") | Select -Last 1
    $vhd = Join-Path $vhdPath $vhdFile
    Test-FileExist -path $vhd
    $infraconfig = Get-DeploymentConfig
    $infraconfig.vhdPath = $vhdPath
    $infraconfig.vhdFile = $vhdFile

    Set-DeploymentConfig -deploymentConfig $infraconfig

    if($setDefaultForWorkload -eq $true) {
        Write-TraceLog "Set-TurnKeySdnDeploymentVhd: Setting default workload VHD to $vhdPath\$vhdFile"
        Set-TurnKeySdnWorkloadImage -os Windows -osSku WindowsLatest -vhdPath $vhdPath -vhdFile $vhdFile
    }
}

function Set-TurnKeySdnDeploymentPath {
    <#
    .SYNOPSIS
    Sets the deployment path for the SDN environment.
    .DESCRIPTION
    The deployment path is the directory where the various VMs are deployed.
    If a path is not provided, a default path is used.
    .PARAMETER DeploymentPath
    The deployment path.
    #>


    param(
        [parameter(Mandatory = $true)][string]$DeploymentPath
    )

    Initialize-StorePath | Out-Null

    $infraconfig = Get-DeploymentConfig
    $infraconfig.vmLocation = $DeploymentPath

    Set-DeploymentConfig -deploymentConfig $infraconfig
}

function Set-TurnKeySdnDeploymentWinBuild {

    <#
    .SYNOPSIS
    Sets a windows build or branch to use for sdn deployment.
    .DESCRIPTION
    If a windows build or branch is not specified, the latest build from the windows build share
    corresponding to the physical host build is used.
    .PARAMETER WindowsBuild
    The specific windows build to use for sdn VM deployment.
    .PARAMETER WindowsBuildBranch
    The windows branch to use for sdn VM deployment. The latest avaialble build from the branch is used.
    #>


    param(
        [parameter(Mandatory = $true, ParameterSetName = "Build")][string]$WindowsBuild,
        [parameter(Mandatory = $true, ParameterSetName = "Branch")][string]$WindowsBuildBranch
    )
    
    Initialize-StorePath | Out-Null

    $infraconfig = Get-DeploymentConfig
    $infraconfig.windowsBuild = $windowsBuild
    $infraconfig.windowsBuildBranch = $WindowsBuildBranch
    Set-DeploymentConfig -deploymentConfig $infraconfig
}

function Set-TurnKeySdnGatewayConfig {
    
    <#
    .SYNOPSIS
    Sets the SDN gateway configuration.
    .PARAMETER Enabled
    Enables or disables the SDN gateway.
    .PARAMETER NodeCount
    The number of SDN gateway nodes to deploy.
    #>


    param(
        [parameter(Mandatory = $false)][bool]$Enabled,
        [parameter(Mandatory = $false)][int]$NodeCount
    )

    Initialize-StorePath | Out-Null

    $sdnConfig = Get-SdnConfig

    if ($PSBoundParameters.ContainsKey("Enabled")) {
        $sdnConfig.gateway.enabled = [string]$Enabled
    }

    if ($PSBoundParameters.ContainsKey("NodeCount")) {
        $sdnConfig.gateway.nodeCount = [string]$NodeCount
    }
    
    Set-TurnKeySdnConfig -sdnConfig $sdnConfig

}


function Set-TurnKeySdnMuxConfig {
    
    <#
    .SYNOPSIS
    Sets the SDN mux configuration.
    .PARAMETER Enabled
    Enables or disables the SDN mux.
    .PARAMETER NodeCount
    The number of SDN mux nodes to deploy.
    #>


    param(
        [parameter(Mandatory = $false)][bool]$Enabled,
        [parameter(Mandatory = $false)][int]$NodeCount
    )

    Initialize-StorePath | Out-Null

    $sdnConfig = Get-SdnConfig

    if ($PSBoundParameters.ContainsKey("Enabled")) {
        $sdnConfig.mux.enabled = [string]$Enabled
    }

    if ($PSBoundParameters.ContainsKey("NodeCount")) {
        $sdnConfig.mux.nodeCount = [string]$NodeCount
    }

    Set-TurnKeySdnConfig -sdnConfig $sdnConfig

}
function Set-TurnKeySdnNCConfig {
    
    <#
    .SYNOPSIS
    Sets the SDN network controller configuration.
    .PARAMETER restName
    The REST endpoint name.
    .PARAMETER restIpAddress
    The REST endpoint IP address.
    .PARAMETER clusterAuthentication
    The cluster authentication mode. Valid values are Kerberos, Certificate.
    .PARAMETER clientAuthentication
    The client authentication mode. Valid values are Kerberos, Certificate.
    .PARAMETER UseSF
    Use Service Fabric as the runtime for the SDN network controller.
    .PARAMETER UseFC
    Use Failover Cluster as the runtime for the SDN network controller.
    .PARAMETER nodecount
    The number of SDN network controller nodes to deploy.
    .PARAMETER PackageLocation
    The location of the SDN network controller package.
    .PARAMETER PowershellModuleRootPath
    The location of the SDN network controller powershell module.
    .PARAMETER DatabaseLocation
    The location to store FC NC database.
    #>


    param(
        [parameter(Mandatory = $false)][string]$restName,
        [parameter(Mandatory = $false)][string]$restIpAddress,
        [parameter(Mandatory = $false)][string]$clusterAuthentication,
        [parameter(Mandatory = $false)][string]$clientAuthentication,
        [parameter(Mandatory = $true, ParameterSetName = "SF")][switch]$UseSF,
        [parameter(Mandatory = $true, ParameterSetName = "FC")][switch]$UseFC,
        [parameter(Mandatory = $false, ParameterSetName = "SF")][string]$nodecount,
        [parameter(Mandatory = $false, ParameterSetName = "FC")][string]$PackageLocation,
        [parameter(Mandatory = $false, ParameterSetName = "FC")][string]$PowershellModuleRootPath,
        [parameter(Mandatory = $false, ParameterSetName = "FC")][string]$DatabaseLocation
    )
    
    Initialize-StorePath | Out-Null

    $sdnConfig = Get-SdnConfig

    if ($PSBoundParameters.ContainsKey("restName")) {
        $sdnConfig.networkController.restName = $restName
    }

    if ($PSBoundParameters.ContainsKey("restIpAddress")) {
        $sdnConfig.networkController.restIpAddress = $restIpAddress
    }

    if ($PSBoundParameters.ContainsKey("clusterAuthentication")) {
        $sdnConfig.networkController.clusterAuthentication = $clusterAuthentication
    }

    if ($PSBoundParameters.ContainsKey("clientAuthentication")) {
        $sdnConfig.networkController.clientAuthentication = $clientAuthentication
    }

    if ($UseSF.IsPresent) {
        $sdnConfig.networkController.runtime = "SF"
    }
    elseif ($UseFC.IsPresent) {
        $sdnConfig.networkController.runtime = "FC"
    }

    if ($PSBoundParameters.ContainsKey("nodecount")) {
        $sdnConfig.networkController.SF.nodecount = $nodecount
    }

    if ($PSBoundParameters.ContainsKey("PackageLocation") -and (-not [String]::IsNullOrEmpty($PackageLocation))) {
        $sdnConfig.networkController.FC.PackageLocation = $PackageLocation
    }

    if ($PSBoundParameters.ContainsKey("PowershellModuleRootPath") -and (-not [String]::IsNullOrEmpty($PowershellModuleRootPath))) {
        $sdnConfig.networkController.FC.PowershellModuleRootPath = $PowershellModuleRootPath
    }

    if ($PSBoundParameters.ContainsKey("DatabaseLocation")) {
        $sdnConfig.networkController.FC.DatabaseLocation = $DatabaseLocation
    }

    Set-TurnKeySdnConfig -sdnConfig $sdnConfig
}

function Get-TurnKeySdnNCConfig {
    
    <#
    .SYNOPSIS
    Gets the SDN network controller configuration.
    #>


    $sdnConfig = Get-SdnConfig
    return $sdnConfig.networkController
}

function Initialize-TurnKeySdnDeployment {
    
    <#
    .SYNOPSIS
    Initializes the environeent for SDN deployment.
    #>


    Initialize-Store
    Initialize-DomainConfiguration
    Initialize-Credentials
    Initialize-WorkloadVMCredentials
    Initialize-HyperVHostConfig
    Initialize-VSwitchConfig
    Initialize-DeploymentPath
    Initialize-DeploymentVhd
    Initialize-NetworkControllerPackage
    Test-DeploymentConfig
    Set-Initialized
}

function Install-TurnKeySdn {
    
    <#
    .SYNOPSIS
    Installs the SDN environment.
    .DESCRIPTION
    See TurnKeySdn.md for details about customizing the deployment.
    #>


    Initialize-StorePath

    if (-not (Test-IsInitialized)) {
        Initialize-TurnKeySdnDeployment
    }

    Test-DeploymentConfig
    
    Initialize-ManagementIPOffset
    $sdnExpressModule = Get-SdnExpressModule
    Import-Module $sdnExpressModule -Force -Scope Global

    $depId = Get-TurnKeySdnDeploymentId
    $infraconfig = Get-DeploymentConfig

    # Fix up the sdnexpress config
    $sdnExpConfig = LoadSdnExpressConfig
    $sdnExpConfig = Initialize-SdnExpressConfig -sdnExpConfig $sdnExpConfig

    $SwitchName = $sdnExpConfig.SwitchName

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtIpPoolStart = $mgmtNetConfig.properties.subnets[0].properties.ipPools[0].properties.startIpAddress
    $hosts = $sdnExpConfig['HyperVHosts'] 
    $ManagementVLANID = $sdnExpConfig.ManagementVLANID
    $cred = Get-TurnKeySdnCred

    $hostNameIPMap = Enable-Hosts -Hosts $hosts -Credential $cred -switchName $SwitchName -MgmtHostNicName $(Get-MgmtNicName) `
        -ManagementVLANID $ManagementVLANID -ManagementSubnet $sdnExpConfig.ManagementSubnet -ManagementIPPoolStart $mgmtIpPoolStart `
        -ManagementGateway $sdnExpConfig.ManagementGateway -ManagementDNS  $sdnExpConfig.ManagementDNS

    $natIP = $sdnExpConfig.ManagementGateway
    $mgmtNicMac = $sdnExpConfig.SDNMacPoolStart
    $sdnExpConfig.SDNMacPoolStart = Get-NextMacAddress -MacAddress $sdnExpConfig.SDNMacPoolStart
    $corpNicMac = $sdnExpConfig.SDNMacPoolStart
    $curMacPoolStart = $corpNicMac
    $sdnExpConfig.SDNMacPoolStart = Get-NextMacAddress -MacAddress $sdnExpConfig.SDNMacPoolStart 

    $CorpSwitchName = $infraconfig.internetSwitchName
    
    $isRRASRouterEnabled = Test-RRASRouterEnabled

    if ($isRRASRouterEnabled) {
        $routerVMName = Get-RouterVMName
        $paNicMac = $sdnExpConfig.SDNMacPoolStart
        $sdnExpConfig.SDNMacPoolStart = Get-NextMacAddress -MacAddress $sdnExpConfig.SDNMacPoolStart

        $paNetConfig = Get-HnvPaNetworkConfig
        $paSubnetPrefix = $($paNetConfig.properties.subnets[0].properties.addressPrefix).Split("/")[1]
        $paGateway = $paNetConfig.properties.subnets[0].properties.defaultGateways[0]

        New-RouterVM -vmName $routerVMName `
            -MgMtIP "$natIP/$($sdnExpConfig.ManagementSubnet.Split("/")[1])" `
            -MgmtNicMac $mgmtNicMac `
            -CorpNicMac $corpNicMac `
            -PANicMac $paNicMac `
            -PAIP "$paGateway/$paSubnetPrefix"
        
        Enable-NatOnRouter -vmName $routerVMName -MgmtNicMac $mgmtNicMac -CorpNicMac $corpNicMac -PANicMac $paNicMac

        # Configure a route on the host vnic to send VIP traffic to the router VM
    
        Set-VipRouteOnHosts

        # Configure BGP peering on router VM
        Enable-BgpOnRouter -vmName $routerVMName `
            -bgpLocalIP $sdnExpConfig.Routers[0].RouterIPAddress `
            -LocalASN $sdnExpConfig.Routers[0].RouterASN
    

        $muxes = $sdnExpConfig['Muxes']

        foreach ($mux in $muxes) {
            Add-MuxPeerToBgp -vmName $routerVMName `
                -bgpPeerName $mux.ComputerName `
                -bgpLocalIP $sdnExpConfig.Routers[0].RouterIPAddress `
                -LocalASN $sdnExpConfig.Routers[0].RouterASN -PeerASN $sdnExpConfig.SDNASN `
                -bgpPeerIP $mux.PAIPAddress
        }
    }

    # Deploy SDN
    try {
        Invoke-SDNExpress -sdnExpConfig $sdnExpConfig
    }
    finally {
        Set-SDNVMNotes
    }

    Add-TurneKeyVMToCluster -Tag "Client=TurnKeySDN"
    Set-PostInstallConfig -sdnExpConfig $sdnExpConfig -hostNameIPMap $hostNameIPMap

    Set-InstallState -state "Installed"
}

function Start-TurnKeySdnTrafficTests {

    param(
        [ValidateSet("LB-INBOUND", "LB-OUTBOUND", "ALL", "VNET", "LB-LBRULE","LIVEMIGRATION")]
        [string] $WorkloadType
    )

    if (-not $(Test-IsInstalled)) {
        throw "Install-TurnKeySdnWorkload: SDN is not installed"
    }

    $workloadConfig = Get-WorkloadConfigPath -WorkloadType "HLK"
    $vhdFile = Get-DeploymentConfig | Select -ExpandProperty vhdFile
    $vhdPath = Get-DeploymentConfig | Select -ExpandProperty vhdPath

    $restName = Get-TurnKeySdnRestEndpoint
    $uri = "https://$restName"

    # must import sdnexpress before starting
    $sdnExpressModule = Get-SdnExpressModule
    Import-Module $sdnExpressModule -Scope Global

    # replay the NCJSON Files
    $restoreReplayScript = Get-RestoreReplayScript

    #$internalWkPath = Join-Path -Path $workloadConfig -ChildPath "jsons"
    & $restoreReplayScript -OperationType Put -BackupDirectoryOrZipFile $workloadConfig `
        -BackupType Manual `
        -Verbose -NCRestEndPoint $restName `
        -Force -Confirm:$false

    $depConfig = Get-DeploymentConfig
    $hyperVHosts = $depConfig.hyperVHosts
    $totalHosts = $hyperVHosts.Count
    $curHost = 0

    $nics = Get-SdnWorkloadNetworkInterfaces -WorkloadConfigPath $workloadConfig
    $slbs = Get-SdnWorkloadLoadBalancers -WorkloadConfigPath $workloadConfig
    $vmWorkloadCredentials = Get-TurnKeySdnWorkloadVmCred
    $hostCredentials = Get-TurnKeySdnCred
    $trafficPatterns =  Get-TrafficPattens

    #process all the NICS first
    [TrafficEndpoint[]] $trafficEndpoints = @()
    $fourGB = 4GB
    $nics | ForEach-Object {
        $nic = $_
        $nicJson = get-content $nic.FullName | ConvertFrom-Json
        $resourceId = $nicJson.resourceId
        $server = $hyperVHosts[$($curHost%$totalHosts)]
        
        $trafficEndpoint = New-TurnKeySdnWorkloadVMUsingSdnExpress -os "Windows" `
            -osSku "WindowsLatest" `
            -ncNicResourceId $resourceId `
            -ncRestEndpoint $restName `
            -hypervHost $server `
            -useDefaultWindows $true `
            -memoryInGB $fourGB `
            -coreCount 4 `
            -enableSecureBoot $false `
            -hypervHostCred $hostCredentials `
            -vmCreds $vmWorkloadCredentials `
            -productKey $depConfig.productKey

        Unblock-AllPortsOnVm -nicResourceId $resourceId -restName $restName
        $trafficEndpoints += $trafficEndpoint
        $curHost++
    }

    # wait for workload VMs to stabilize
    Write-TraceLog "Install-HlkWorkload: Completed creating VM $vmName on host $hypervHost..waiting for boot"
    foreach($trafficEndpoint in $trafficEndpoints) {
            
        Test-TenantVirtualMachine -vmname $trafficEndpoint.vmName `
            -hostName $trafficEndpoint.HostName `
            -vmCred $trafficEndpoint.VmCredential `
            -hostCred $trafficEndpoint.HostCredential
    }

    # initialze VMs & prepare for traffic
    Initialize-VmForCtstraffic -trafficEndpoints $trafficEndpoints  -vmCred $vmWorkloadCredentials -hostCred $hostCredentials

    # test vnet traffic
    if($workloadType -eq "VNET" -or $workloadType -eq "ALL") {

        if($null -eq $trafficEndpoints -or $trafficEndpoints.count -eq 0) {
            throw "No VMs found to run VNET traffic"
        }

        Start-IntraVmTraffic -trafficEndpoints $trafficEndpoints `
            -trafficPattern $trafficPatterns `
            -vmCred $vmWorkloadCredentials `
            -hostCred $hostCredentials
    }

    foreach($slb in $slbs) {

        # build a map of all the LB endpoints
        $loadbalancerEndpoints = @()
        $lb = get-content $slb.FullName | ConvertFrom-Json
        $loadbalancerEndpoints = Get-EndpointsFromLoadBalancer -lb $lb.ResourceId -uri $uri

        Write-TraceLog "Start-SdnHlkTrafficTests: Completed parsing loadbalancer endpoints. Summary:"
        $loadbalancerEndpoints | ConvertTo-Json -Depth 10 | Write-Host

        if($workloadType -eq "LB-LBRULE" -or $workloadType -eq "ALL") {

            if($loadbalancerEndpoints["LOADBALANCER_TRAFFIC_RULES"] -ne $null) {

                Start-SLBTraffic -TargetType LoadbalancerRules `
                    -vips $loadbalancerEndpoints `
                    -trafficPatterns  $trafficPatterns  `
                    -vmCred $vmWorkloadCredentials `
                    -hostCred $hostCredentials `
                    -uri $uri
            } else {
                Write-TraceLog "Start-SdnHlkTrafficTests: No loadbalancer rules found. Skipping LB-LBRULE traffic"
            }
        }
        if($workloadType -eq "LB-OUTBOUND" -or $workloadType -eq "ALL") {

            if($loadbalancerEndpoints["OUTBOUND_NAT_TRAFFIC_RULES"] -ne $null) {

                Start-SLBTraffic -TargetType Outbound `
                    -vips $loadbalancerEndpoints `
                    -trafficPatterns  $trafficPatterns  `
                    -vmCred $vmWorkloadCredentials `
                    -hostCred $hostCredentials `
                    -uri $uri
                    
            } else {

                Write-TraceLog "Start-SdnHlkTrafficTests: No loadbalancer outbound rules found. Skipping LB-OUTBOUND traffic"
            }
        }
        if($workloadType -eq "LB-INBOUND" -or $workloadType -eq "ALL") {

            if($loadbalancerEndpoints["INBOUND_NAT_TRAFFIC_RULES"] -ne $null) {

                Start-SLBTraffic -TargetType Inbound `
                    -vips $loadbalancerEndpoints `
                    -trafficPatterns  $trafficPatterns  `
                    -vmCred $vmWorkloadCredentials `
                    -hostCred $hostCredentials `
                    -uri $uri

            } else {
                    
                Write-TraceLog "Start-SdnHlkTrafficTests: No loadbalancer inbound rules found. Skipping LB-INBOUND traffic"
            }
        }

        # Noticed LM tests failing due to lack of documentation/S2D config issues.
        # Disabling LM tests temporarily until proper documentation is available.
        # As LM test are mainly for SDN control plane validation, this is not a P0 test for HLK.
        if($workloadType -eq "LIVEMIGRATION" -or $workloadType -eq "ALL") {

            if($null -eq $trafficEndpoints -or $trafficEndpoints.count -eq 0) {
                throw "No VMs found to run VNET traffic"
            }

            # Start-SDNVmMigrationValidation -trafficEndpoints $trafficEndpoints -percentageOfVmsToMove 100
        }

        Write-SdnExpressLog "$WorkloadType passed!"
    }
    # as of now if there is a failure, the exception will be thrown and the script will exit

}


function Install-TurnKeySdnWorkload {
    <#
    .SYNOPSIS
    Installs the SDN workloads.
    .DESCRIPTION
    Workloads are deployed based on the json configuration files in the workload directory corresponding to the workload type.
    All loadbalancers configured will have connectivity from the physical host to the workload VMs.
    VMs will have connectivity to the internet if outbound NAT is enabled.
    If the workload type is Gateway, a datacenter gateway VM will configured for the gateway connection.
    VM private IP addresses will be reachable over the gateway connection from the physical host.
    .PARAMETER WorkloadType
    Workload type to install. Valid values are LoadBalancer, VNET, LogicalNetwork, Gateway, None.
    .PARAMETER WorkloadCustomPath
    A custom path with jsons to deploy the workload. If not specified, the default path for the workload type is used.
    #>
    
    param(
        [ValidateSet("LoadBalancer", "VNET", "LogicalNetwork", "Gateway", "None")]
        [Parameter(Mandatory = $true, ParameterSetName = "Inbuilt")] $WorkloadType,
        [Parameter(Mandatory = $false, ParameterSetName = "Inbuilt")] $WorkloadCustomPath
    )

    if (-not $(Test-IsInstalled)) {
        Write-TraceLog "Install-TurnKeySdnWorkload: SDN is not installed" -Warning
        return
    }

    Initialize-SdnWorkloadDeploymentPath
    $vhd = Get-DefaultWorkloadMarinerVhd

    if ($null -ne $vhd) {
        Set-TurnKeySdnWorkloadImage -vhdPath $vhd[0] -vhdFile $vhd[1] -os Linux -osSku Mariner
    }

    Test-WorkloadConfig -workLoadConfig $WorkloadCustomPath
    
    Set-VipRouteOnHosts

    $restoreReplayScript = Get-RestoreReplayScript

    if ([String]::IsNullOrEmpty($WorkloadCustomPath)) {
        $internalWkPath = Get-WorkloadConfigPath -WorkloadType $WorkloadType
    }
    else {
        $internalWkPath = $WorkloadCustomPath
    }

    $restName = Get-TurnKeySdnRestEndpoint

    $sdnExpressModule = Get-SdnExpressModule
    Import-Module $sdnExpressModule -Scope Global
    Update-WorkloadIPAddresses -WorkloadConfigPath $internalWkPath

    Update-WorkloadSecrets -WorkloadConfigPath $internalWkPath

    & $restoreReplayScript -OperationType Put -BackupDirectoryOrZipFile $internalWkPath -BackupType Manual -Verbose -NCRestEndPoint $restName -Force -Confirm:$false    

    $logicalNetworks = Get-SdnWorkloadLogicalNetworks -WorkloadConfigPath $internalWkPath
    foreach ($lnet in $logicalNetworks) {
        $lnetJson = get-content $lnet.FullName | ConvertFrom-Json
        $resourceId = $lnetJson.resourceId
        Enable-LogicalNetworkDefaultGateway -LogicalNetworkResourceId $resourceId
    }

    $nics = Get-SdnWorkloadNetworkInterfaces -WorkloadConfigPath $internalWkPath

    if ($null -eq $nics ) {
        Write-SdnExpressLog "No network interfaces found. Nothing to deploy."
        return
    }  

    $depConfig = Get-DeploymentConfig
    $hyperVHosts = $depConfig.hyperVHosts
    $totalHosts = $hyperVHosts.Count
    $curHost = 0
    $nics | ForEach-Object {
        $nic = $_
        $nicJson = get-content $nic.FullName | ConvertFrom-Json
        $resourceId = $nicJson.resourceId
        $server = $hyperVHosts[$($curHost % $totalHosts)]
        New-TurnKeySdnWorkloadVM -os Linux -osSku Mariner -ncNicResourceId $resourceId -ncRestEndpoint $restName -hypervHost $server -AllComputers $hyperVHosts
        $curHost++
    }

    if ($WorkloadType -eq "Gateway") {
        Enable-GatewayWorkload -WorkloadConfigPath $internalWkPath
    }

    Add-TurneKeyVMToCluster -Tag "Role=Workload"

    Write-TraceLog "Workload deployment completed successfully for $WorkloadType"
}

function Uninstall-TurnKeySdn {
    <#
    .Synopsis
    Uninstalls the SDN environment.
    PARAMETER DeploymentId
    Optional the deployment id to uninstall. If not specified, the current deployment is uninstalled.
    PARAMETER KeepInfraVMs
    Optional. If specified, for a cluster deployment the Router VM is not deleted. This can be used to speed up the next deployment.
    For standalone deployments, this parameter is ignored.
    #>

    param (
        [parameter(Mandatory = $false)][string] $DeploymentId,
        [parameter(Mandatory = $false)][switch] $KeepInfraVMs,
        [parameter(Mandatory = $false)][switch] $Force
    )

    Initialize-StorePath

    $sdnExpressModule = Get-SdnExpressModule
    Import-Module $sdnExpressModule -Scope Global

    $isCluster = Test-IsCluster

    if (-not $Force.IsPresent) {
        Write-TraceLog "Uninstall-TurnKeySdn: Uninstalling deployment. All configurations and VMs will be deleted." -Warning

        if (-not $KeepInfraVMs.IsPresent -and $isCluster) {
            Write-TraceLog "If you plan to reinstall SDN, use -KeepInfraVMs to keep the Router VM to speed up the next deployment."
        }

        $confirm = Read-Host "Uninstall-TurnKeySdn: Are you sure you want to proceed:(y/N)"
        if ($confirm -ne 'y') {
            return
        }
    }

    Remove-FCNC 
    Remove-SdnConfig
    
    Remove-WorkloadVMs
    Remove-SdnVMs

    if ($KeepInfraVMs -and $isCluster) {
        Remove-RouterVMWorkloadAdapters
    }
    else {
        Remove-InfraVMs
    }

    Remove-PhysicalHostVnic
    Remove-DeploymentCertificates    
    Remove-HostRegistryKeys
    
    if (-not $($KeepInfraVMs.IsPresent)){
        Remove-TurnKeySdnStore
    } else {
        Remove-TurnKeySdnStore -SkipStore:$true
    }

    Undo-StorePathInitialization
}


function Uninstall-TurnKeySdnWorkload {
    <#
    .SYNOPSIS
    Uninstalls the SDN workload.
    #>

    param (
        [parameter(Mandatory = $false)][switch] $Force
    )
    if (-not $Force.IsPresent) {
        Write-TraceLog "Uninstall-TurnKeySdnWorkload: Uninstalling workloads. All workload VMs and NC resources will be deleted." -Warning
        $confirm = Read-Host "Uninstall-TurnKeySdnWorkload: Are you sure you want to proceed:(y/N)"
        if ($confirm -ne 'y') {
            return
        }
    }

    if (-not (Test-IsInstalled)) {
        Write-TraceLog "Uninstall-TurnKeySdnWorkload: SDN is not installed"
        return
    }

    Initialize-StorePath
    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"
    $cred = Get-TurnKeySdnCred
    Remove-NCResources -NCUri $restUrl -Credential $cred
    
    Remove-WorkloadVMs
    Remove-RouterVMWorkloadAdapters
    Remove-TestRoutesOnHostVnic

}

function Get-TurnKeySdnHNVPANetwork {
    <#
    .SYNOPSIS
    Gets the HNV PA network configuration.
    #>

    $hnvpaNetworkFile = Get-HnvPaNetworkConfig -RandomizeAddress $false
    return $hnvpaNetworkFile
}

function Get-TurnKeySdnManagementNetwork {
    <#
    .SYNOPSIS
    Gets the management network configuration.
    #>

    $mgmtNetwork = Get-MgmtNetworkConfig -RandomizeAddress $false
    return $mgmtNetwork  
}

function Get-TurnKeySdnPublicVIPNetwork {
    <#
    .SYNOPSIS
    Gets the public VIP network configuration.
    #>

    $publicVipNetwork = Get-PublicVipNetworkConfig
    return $publicVipNetwork  
}

function Get-TurnKeySdnPrivateVIPNetwork {
    <#
    .SYNOPSIS
    Gets the private VIP network configuration.
    #>

    $privateVipNetwork = Get-PrivateVipNetworkConfig
    return $privateVipNetwork  
}

function Set-TurnKeySdnHNVPANetwork {
    <#
    .SYNOPSIS
    Sets the HNV PA network configuration.
    . PARAMETER Network
    The HNV PA network configuration to set.
    #>

    param(
        [parameter(Mandatory = $true)][PSCustomObject]$Network
    )

    Test-DeploymentLogicalNetworkConfig -Network $Network
    Set-HnvPaNetworkConfig -Network $Network
}

function Set-TurnKeySdnManagementNetwork {
    <#
    .SYNOPSIS
    Sets the management network configuration.
    . PARAMETER Network
    The management network configuration to set.
    #>

    param(
        [parameter(Mandatory = $true)][PSCustomObject]$Network
    )

    Test-DeploymentLogicalNetworkConfig -Network $Network
    Set-MgmtNetworkConfig -Network $Network
}

function Set-TurnKeySdnPublicVIPNetwork {
    <#
    .SYNOPSIS
    Sets the public network configuration.
    . PARAMETER Network
    The public network configuration to set.
    #>

    param(
        [parameter(Mandatory = $true)][PSCustomObject]$Network
    )

    Test-DeploymentLogicalNetworkConfig -Network $Network
    Set-PublicVipNetworkConfig -Network $Network
}

function Set-TurnKeySdnPrivateVIPNetwork {
    <#
    .SYNOPSIS
    Sets the private network configuration.
    . PARAMETER Network
    The private network configuration to set.
    #>

    param(
        [parameter(Mandatory = $true)][PSCustomObject]$Network
    )

    Test-DeploymentLogicalNetworkConfig -Network $Network
    Set-PrivateVipNetworkConfig -Network $Network
}

function Get-TurnKeySdnDeploymentConfig {
    <#
    .SYNOPSIS
    Gets the deployment configuration from deployment.json
    #>

    return (Get-DeploymentConfig)
}

function Set-TurnKeySdnDeploymentConfig {
    <#
    .SYNOPSIS
    Sets the deployment configuration.
    . PARAMETER Config
    The deployment configuration to set.
    #>

    param(
        [parameter(Mandatory = $true)][PSCustomObject]$Config
    )

    Test-TorVMConfig -Config $Config.torVMConfig
    Set-DeploymentConfig -deploymentConfig $Config
}
function Set-TurnKeySdnSwitchName {
    param(
        [parameter(Mandatory=$true)][string]$SwitchName
    )
    $sdnConfig = Get-SdnConfig
    $sdnConfig.networkController.switchName = $SwitchName
    Set-TurnKeySdnConfig -sdnConfig $sdnConfig
}

function Set-SdnExpressPath {
    param(
        [parameter(Mandatory=$true)][string]$SdnExpressPath
    )
    Set-SdnExpressPathInternal -sdnExpressPath $SdnExpressPath
}

function Set-TurnKeySdnNetworkConfiguration {
    <#
    .SYNOPSIS
    Set a custom network configuration to use for the deployment.
    . PARAMETER NetworkConfigPath
    Directory containing the network configuration files.
    #>

    param(
        [parameter(Mandatory = $true)][string]$NetworkConfigPath
    )

    Initialize-StorePath

    $hnvpaNetworkFile = Join-Path $NetworkConfigPath "logicalnetworks\hnvpa.json"
    $mgmtNetworkFile = Join-Path $NetworkConfigPath "logicalnetworks\management.json"
    $greNetworkFile = Join-Path $NetworkConfigPath "logicalnetworks\grevip.json"
    $publicVIPNetworkFile = Join-Path $NetworkConfigPath "logicalnetworks\publicvip.json"
    $macpoolFile = Join-Path $NetworkConfigPath "macpools\defaultMacPool.json"

    $hnvpaConfig = Get-Config -File $hnvpaNetworkFile
    $mgmtConfig = Get-Config -File $mgmtNetworkFile
    $greConfig = Get-Config -File $greNetworkFile
    $publicVIPConfig = Get-Config -File $publicVIPNetworkFile
    $macpoolConfig = Get-Config -File $macpoolFile    

    Write-TraceLog "Set-TurnKeySdnNetworkConfiguration: Setting custom network configuration from $NetworkConfigPath"

    $config = $hnvpaConfig | ConvertTo-Json -Depth 10
    Write-TraceLog "Set-TurnKeySdnNetworkConfiguration: Setting HNV PA network config"
    Set-TurnKeySdnHNVPANetwork -Network $hnvpaConfig
    Write-TraceLog "Set-TurnKeySdnNetworkConfiguration: Setting management network config"
    Set-TurnKeySdnManagementNetwork -Network $mgmtConfig
    Write-TraceLog "Set-TurnKeySdnNetworkConfiguration: Setting publicvip network config"
    Set-PublicVipNetworkConfig -Network $publicVIPConfig    
    Write-TraceLog "Set-TurnKeySdnNetworkConfiguration: Setting grevip network config"
    Set-GreVipNetworkConfig -Network $greConfig

    Write-TraceLog "Set-TurnKeySdnNetworkConfiguration: Setting macpool config"
    Set-MacPoolConfig -Config $macpoolConfig
}

function Set-TurnkeySdnAddressRandmoizerSeed {
    <#
    .SYNOPSIS
    Sets the seed for the address randomizer. IP address and MAC addresses are generated based on the seed.
    . PARAMETER Seed
    The seed to use for the address randomizer. Valid values are 0 to 60.
    #>

    param(
        [parameter(Mandatory = $true)][int]$Seed
    )

    if ($Seed -lt 0 -or $Seed -gt 60) {
        throw "Seed must be between 0 and 60"
    }   

    Set-AddressRandmoizerSeed -Seed $Seed
}

function Set-TurnkeySdnDeploymentId {
    <#
    .SYNOPSIS
    Sets the deployment id for the SDN deployment.
    . PARAMETER Id
    The deployment id to set. Must be string less than 8 characters.
    #>


    param(
        [parameter(Mandatory = $true)][string]$Id
    )
    
    if ($(Test-IsInstalled)) {
        Write-TraceLog "Deployment ID cannot be changed post install" -Warning
        throw "NOT_SUPPORTED"
    }

    if (-not $(Test-ValidDepId -Id $Id)) {
        throw "ID $Id is invalid or in use"
    }

    Set-DeploymentId -Id $Id
}

function Get-SdnHLKOfflinePackage {
    <#
    .SYNOPSIS
    Gets the HLK offline package.
    .DESCRIPTION
    Execute this script on a machine with internet access to download the required files. The HLK offline package contains the TurnKeySDN module, SDNExpress module, RestoreReplay.ps1 script and ctstraffic.exe. Copy the HLK offline package to the HLK Client Machine.
    Package should be placed under the same folder as the WorkingDir parameter of "PrivateCloudSimulator - Device.Network.LAN.AzureStack" test.
    . PARAMETER Destination
    [Optional] The destination path to copy the HLK offline package. The default location is "$env:SystemDrive\Tools\Deployment".
    #>


    param(
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Destination = "$env:SystemDrive\Tools\Deployment"
    )

    $defaultPath = "$env:SystemDrive\Tools\Deployment"

    $parentDirectory = Split-Path $Destination -Parent
    if ($defaultPath -ieq $Destination) {
        New-Item $parentDirectory -ErrorAction SilentlyContinue -ItemType Directory -Force | Out-Null
    } else {
        # For custom path parent directory should exist
        if (-not [String]::IsNullOrEmpty($parentDirectory) -and -not $(Test-Path $parentDirectory)) {
            throw "Get-HLKOfflinePackage: Directory $parentDirectory does not exist, please create this directory and retry the operation."
        }    
    }  

    if (-not $(Test-Path $Destination)) {
        New-Item $Destination -ErrorAction Stop -ItemType Directory -Force | Out-Null
    }

    $sdnExpPath = Get-SdnExpressPath -SkipConfigPath
    if ([String]::IsNullOrEmpty($sdnExpPath) -or -not $(Test-Path $sdnExpPath)) {
        throw "Get-HLKOfflinePackage: SDNExpress module not found. Please retry after installing SDNExpress module. (Install-Module -Name SdnExpress)"
    }

    $restoreReplayScriptPath = Get-RestoreReplayScriptPath -SkipConfigPath

    if ([String]::IsNullOrEmpty($restoreReplayScriptPath) -or -not $(Test-Path $restoreReplayScriptPath)) {
        throw "Get-HLKOfflinePackage: RestoreReplay script not found. Please retry after installing RestoreReplay script(Install-Script -Name RestoreReplay)"
    }

    Import-Module -Name SdnDiagnostics -ErrorAction SilentlyContinue
    $sdnDiagModule = Get-Module SdnDiagnostics
    if ($null -ne $module) {
        throw "Get-HLKOfflinePackage: SdnDiagnostics module not found. Please retry after installing SdnDiagnostics script(Install-Module -Name SdnDiagnostics)"
    }    
    $sdnDiagPath = $sdnDiagModule.ModuleBase

    $restoreReplayScript = Get-RestoreReplayScript    

    # copy contents of TurnKeySDN module to working directory
    $turnkeySdnDest = Join-Path $Destination TurnKeySdn
    New-Item $turnkeySdnDest -ErrorAction SilentlyContinue -ItemType Directory -Force | Out-Null
    Write-Host "Found TurnkeySDN module at $PSScriptRoot"
    Write-Host "Copying TurnkeySDN module to $turnkeySdnDest"
    Copy-Item -Path "$PSScriptRoot\*" -Destination $turnkeySdnDest -Recurse -Force -ErrorAction Stop   

    $sdnExpDest = Join-Path $Destination SdnExpress
    New-Item $sdnExpDest -ErrorAction SilentlyContinue -ItemType Directory -Force | Out-Null

    Write-Host "Found SDNExpress module at $sdnExpPath"
    Write-Host "Copying SDNExpress to $sdnExpDest"
    Copy-Item -Path "$sdnExpPath\*" -Destination $sdnExpDest -Recurse -Force -ErrorAction Stop

    # Copy RestoreReplay.ps1 script
    $restoreReplayDest = Join-Path $Destination BCDR
    New-Item $restoreReplayDest -ErrorAction SilentlyContinue -ItemType Directory -Force  | Out-Null    
    Write-Host "Found RestoreReplayScript at $restoreReplayScript"
    Write-Host "Copying RestoreReplayScript to $restoreReplayDest"
    Copy-Item -Path $restoreReplayScript -Destination $restoreReplayDest -Recurse -Force -ErrorAction Stop

    # Copy SdnDiagnostics module
    $sdnDiagDest = Join-Path $Destination SdnDiagnostics
    New-Item $sdnDiagDest -ErrorAction SilentlyContinue -ItemType Directory -Force | Out-Null
    Write-Host "Found SdnDiagnostics module at $sdnDiagPath"
    Write-Host "Copying SdnDiagnostics to $sdnDiagDest"
    Copy-Item -Path "$sdnDiagPath\*" -Destination $sdnDiagDest -Recurse -Force -ErrorAction Stop


    # copy ctstraffic.exe
    Write-Host "Downloading Ctstraffic.exe to $Destination"
    $ctsTrafficDest = Join-Path $Destination "ctstraffic.exe"
    curl https://github.com/microsoft/ctsTraffic/raw/master/Releases/2.0.3.3/x64/ctsTraffic.exe -OutFile $ctsTrafficDest

    Write-Host "Generated HLK offline package at $Destination. Please copy the contents of $Destination to all the HLK client machines and place them under the folder corresponding to the 'WorkingDir' parameter of 'PrivateCloudSimulator - Device.Network.LAN.AzureStack' test" -ForegroundColor Green
}

function Set-TurnKeySdnNetIntent {
    <#
    .SYNOPSIS
    Sets the network intent for the turnkey SDN deployment.
    .DESCRIPTION
    This command is used to configure the network intent for the turnkey SDN deployment.
    A Compute + storage intent is created for the Test NICs with the specified interface description pattern.
    If Corp NICs are present, a Management intent is created for the Corp NICs.
    .PARAMETER TestNicIfDescPattern
    The interface description pattern to match the Test NICs to configure for ATC.
    .PARAMETER storageVLAN
    The storage VLANs to configure for ATC.
    .PARAMETER Force
    If specified, the command will remove existing intent and configure new intent.
    #>

    param(
        [Parameter(Mandatory=$false)] [string] $TestNicIfDescPattern = "Mellanox*",
        [Parameter(Mandatory=$false)] [Array] $storageVLAN = @(8,9),
        [Parameter(Mandatory=$false)] [switch] $Force
    )
    
    $isCluster = Test-IsCluster
    
    if (-not $isCluster) {
        Write-TraceLog "Set-TurnKeySdnNetIntent: This command is only supported for cluster deployments, skipping ATC configuration" -Warning
        return
    }

    if ($null -ne $(Get-VMSwitch -ErrorAction SilentlyContinue)) {
        Write-TraceLog "Set-TurnKeySdnNetIntent: Existing VSwitch found, skipping ATC configuration" -Warning
        return
    }

    $nics = Get-NetAdapter -InterfaceDescription $TestNicIfDescPattern

    if ($null -eq $nics) {
        Write-TraceLog "Set-TurnKeySdnNetIntent: No NICs found with interface description $TestNicIfDescPattern, skipping ATC configuration" -Warning
        return
    }

    $hosts = Get-TurnKeySdnHyperVHosts
    $cred = Get-TurnKeySdnCred

    Set-SdnNetIntent -ComputerName $hosts -Credential $cred -ifDescPattern $TestNicIfDescPattern -storageVLAN $storageVLAN -Force:$Force.IsPresent
}

function Set-TurnKeySdnTorVMConfig {
    param(
        [parameter(Mandatory=$true)][PSCustomObject]$Config
    )

    Test-TorVMConfig -Config $Config
    Set-TorVMConfig -Config $Config
}

function Get-TurnKeySdnTorVMConfig {
    return (Get-TorVMConfig)
}

function Test-IsTurnKeySdnInstalled {
    return $(Test-IsInstalled)
}