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
    )

    Initialize-StorePath | Out-Null

    $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 clientAuthenticaion
    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]$clientAuthenticaion,
        [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("clientAuthenticaion")) {
        $sdnConfig.networkController.clientAuthenticaion = $clientAuthenticaion
    }

    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

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

    $depId = Get-TurnKeySdnDeploymentId

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

    $SwitchName = $sdnExpConfig.SwitchName

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $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 `
        -ManagementGateway $sdnExpConfig.ManagementGateway -ManagementDNS  $sdnExpConfig.ManagementDNS `
        -IsFcNc  $sdnExpConfig.UseFCNC

    $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
    
    $infraconfig = Get-DeploymentConfig
    $CorpSwitchName = $infraconfig.internetSwitchName
    
    $routerVMName = Get-RouterVMName
    $paNicMac = $sdnExpConfig.SDNMacPoolStart
    $sdnExpConfig.SDNMacPoolStart = Get-NextMacAddress -MacAddress $sdnExpConfig.SDNMacPoolStart
    New-RouterVM -vmName $routerVMName `
        -MgMtIP "$natIP/$($sdnExpConfig.ManagementSubnet.Split("/")[1])" `
        -MgmtNicMac $mgmtNicMac `
        -CorpNicMac $corpNicMac `
        -PANicMac $paNicMac `
        -PAIP "$($sdnExpConfig.PAGateway)/$($sdnExpConfig.PASubnet.Split("/")[1])"

    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-PublicVipRouteOnHosts

    # 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-SdnHlkTrafficTests {

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

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

    $workloadConfig = $internalWkPath = 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 = @()
    $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 4 `
            -coreCount 4 `
            -enableSecureBoot $false `
            -hypervHostCred $hostCredentials `
            -vmCreds $vmWorkloadCredentials

        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
    Init-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"
            }
        }
        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 ($vhd -ne $null) {
        Set-TurnKeySdnWorkloadImage -vhdPath $vhd[0] -vhdFile $vhd[1] -os Linux -osSku Mariner
    }

    Test-WorkloadConfig -workLoadConfig $WorkloadCustomPath
    
    Set-PublicVipRouteOnHosts

    $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 ($nics -eq $null ) {
        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, the BGP and NAT VMs are not deleted. This can be used to speed up the next deployment.
    #>

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

    Initialize-StorePath

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

        if (-not $KeepInfraVMs.IsPresent) {
            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
        }
    }

    try {
        Import-Module -name NetworkControllerFc -ErrorAction SilentlyContinue
        $module = Get-Module -name NetworkControllerFc

        if ($module) {
            Uninstall-NetworkControllerOnFailoverCluster
        } else {
            Write-TraceLog "Uninstall-TurnKeySdn: NetworkControllerFc Module not found, skipping uninstall"
        }
    } catch {
        Write-TraceLog "Uninstall-TurnKeySdn: Error ($_)"
    }

    Remove-SdnConfig

    Remove-WorkloadVMs
    Remove-SdnVMs

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

    Remove-PhysicalHostVnic
    Remove-DeploymentCertificates

    Remove-TurnKeySdnStore
    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-WorkloadVMs
    Remove-RouterVMWorkloadAdapters
    Remove-TestRoutesOnHostVnic
    Remove-NCResources -NCUri $restUrl -Credential $cred
}

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 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 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
    )

    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
}