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" } } 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 50 } 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 $(Get-CtsTrafficGitHubUri) -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) } # SIG # Begin signature block # MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAz49kH2s+2FwBC # 04ZIlkJs7xlxnQJCUgQJm0iBUf1jdaCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFdi1oyNqu3mSlCs9VaudIj7 # 6l4hCR7D8724PWYT+iHpMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEACeQw0KCtMrYp42mrGluI2Yvi/cQgyBNbdYch5dtXlWFIiul/QcQ0TFE2 # uWZi1b9CR1z+0uA0vLlL9PjSsqJtlnr6c45kCO7t8AICrtN1lrfDzyZfUTtBrHHF # xXMbUnkdPa/H/tIQtEPKKjWWjEShPuSdN4oumt05HwYHpIJg4XBTbqfL5LYWD2xl # rLwIdHRCJ1JWa/LMbB2G0fxzNb8iDpiqgem1nbI9viGlmAXLRVsymNoW12xN8Slu # KQjosjRpTMVJdlokA4yF9eAJer0iW2VlmvUNK9+lf7GFFOsi4CqkvU1VK1I5at4A # +ODPPnW82qvtFqNCu9wdSHLiaVLglaGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC # FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq # hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCBmYPSQKugn2ZhHYcCVtSEFbR1HUBk8Jfzgvpdg9TCHwwIGZnL0nrwq # GBMyMDI0MDcwMjIyMjYwNC40NjdaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO # Ojg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAHdXVcdldStqhsAAQAAAd0wDQYJ # KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx # MDEyMTkwNzA5WhcNMjUwMTEwMTkwNzA5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl # cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4NkRGLTRC # QkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKhOA5RE6i53nHURH4lnfKLp # +9JvipuTtctairCxMUSrPSy5CWK2DtriQP+T52HXbN2g7AktQ1pQZbTDGFzK6d03 # vYYNrCPuJK+PRsP2FPVDjBXy5mrLRFzIHHLaiAaobE5vFJuoxZ0ZWdKMCs8acjhH # UmfaY+79/CR7uN+B4+xjJqwvdpU/mp0mAq3earyH+AKmv6lkrQN8zgrcbCgHwsqv # vqT6lEFqYpi7uKn7MAYbSeLe0pMdatV5EW6NVnXMYOTRKuGPfyfBKdShualLo88k # G7qa2mbA5l77+X06JAesMkoyYr4/9CgDFjHUpcHSODujlFBKMi168zRdLerdpW0b # BX9EDux2zBMMaEK8NyxawCEuAq7++7ktFAbl3hUKtuzYC1FUZuUl2Bq6U17S4CKs # qR3itLT9qNcb2pAJ4jrIDdll5Tgoqef5gpv+YcvBM834bXFNwytd3ujDD24P9Dd8 # xfVJvumjsBQQkK5T/qy3HrQJ8ud1nHSvtFVi5Sa/ubGuYEpS8gF6GDWN5/KbveFk # dsoTVIPo8pkWhjPs0Q7nA5+uBxQB4zljEjKz5WW7BA4wpmFm24fhBmRjV4Nbp+n7 # 8cgAjvDSfTlA6DYBcv2kx1JH2dIhaRnSeOXePT6hMF0Il598LMu0rw35ViUWcAQk # UNUTxRnqGFxz5w+ZusMDAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUbqL1toyPUdpF # yyHSDKWj0I4lw/EwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD # VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j # cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG # CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw # MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD # CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAC5U2bINLgXIHWbM # cqVuf9jkUT/K8zyLBvu5h8JrqYR2z/eaO2yo1Ooc9Shyvxbe9GZDu7kkUzxSyJ1I # ZksZZw6FDq6yZNT3PEjAEnREpRBL8S+mbXg+O4VLS0LSmb8XIZiLsaqZ0fDEcv3H # eA+/y/qKnCQWkXghpaEMwGMQzRkhGwcGdXr1zGpQ7HTxvfu57xFxZX1MkKnWFENJ # 6urd+4teUgXj0ngIOx//l3XMK3Ht8T2+zvGJNAF+5/5qBk7nr079zICbFXvxtidN # N5eoXdW+9rAIkS+UGD19AZdBrtt6dZ+OdAquBiDkYQ5kVfUMKS31yHQOGgmFxuCO # zTpWHalrqpdIllsy8KNsj5U9sONiWAd9PNlyEHHbQZDmi9/BNlOYyTt0YehLbDov # mZUNazk79Od/A917mqCdTqrExwBGUPbMP+/vdYUqaJspupBnUtjOf/76DAhVy8e/ # e6zR98PkplmliO2brL3Q3rD6+ZCVdrGM9Rm6hUDBBkvYh+YjmGdcQ5HB6WT9Rec8 # +qDHmbhLhX4Zdaard5/OXeLbgx2f7L4QQQj3KgqjqDOWInVhNE1gYtTWLHe4882d # /k7Lui0K1g8EZrKD7maOrsJLKPKlegceJ9FCqY1sDUKUhRa0EHUW+ZkKLlohKrS7 # FwjdrINWkPBgbQznCjdE2m47QjTbMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ # mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh # dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1 # WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB # BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK # NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg # fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp # rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d # vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9 # 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR # Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu # qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO # ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb # oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6 # bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t # AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW # BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb # UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz # aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku # aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA # QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2 # VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu # bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw # LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt # MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q # XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6 # U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt # I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis # 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp # kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0 # sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e # W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ # sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7 # Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0 # dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ # tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh # bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4 # NkRGLTRCQkMtOTMzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUANiNHGWXbNaDPxnyiDbEOciSjFhCggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOoulUgwIhgPMjAyNDA3MDIyMzAzMzZaGA8yMDI0MDcwMzIzMDMzNlowdDA6Bgor # BgEEAYRZCgQBMSwwKjAKAgUA6i6VSAIBADAHAgEAAgIHOTAHAgEAAgIRPTAKAgUA # 6i/myAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID # B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBANxaZtOcbDmRyjGvaPdP # n+RLoQgOFO1kWA2+asU1lME/rXRqlV433LNuPueWcUSC6uFeM3QFepgOZyXS0dOc # PvMjejjGHEVxkVZvfVrQ6CzWiByuPaWSnmfKrVdcvVSzXi7cCh1t93AMWygrUMBw # R2g2evvT88vNjH7W8FzJXgoMMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTACEzMAAAHdXVcdldStqhsAAQAAAd0wDQYJYIZIAWUDBAIB # BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx # IgQgw7ybX93FQePjjgN9iipdu9g4U5PtRD22aKHprrZGq/8wgfoGCyqGSIb3DQEJ # EAIvMYHqMIHnMIHkMIG9BCBh/w4tmmWsT3iZnHtH0Vk37UCN02lRxY+RiON6wDFj # ZjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB3V1X # HZXUraobAAEAAAHdMCIEIGc2ZfSTe0iQlzGIllfUpAVYZmoNf6km8tCGlbAoUR6L # MA0GCSqGSIb3DQEBCwUABIICAKF1xoUBPWoBZ1KdFom5mCXq6rEby7vrKGiBacXZ # e5/kP+bEPJ72STOOnb7AJiA+reW/3zUqrs+vpUSIFvezOd5ehv5vIwn1Nl9cZeCU # 35b+OwxGhuEEfFmXFF3lo743e3l/hpgcQw53vhTUGtxpN5ALAErGxyLd6satiKQK # y07QHHUtXgEM8754/Ubz7N/7ojlzXWYaD6TfJaedELuoOgaotD/8MyrLfvizDI2x # Wclt6ottygkf7nOc2kEJmwT1LHIVCZlOOagNpbf/o5/rBFDfB8mYOtHMUS1EMYME # 9kxuR0Gy/xPj0lBwfVWFavBnqHU8a0AruGLLi6RkyUyqFDHxFn5jLtteOOqgT0c8 # e+efym92u2XCOxPKqj0vRGUbDDZ4sLLc3s/6pSoZeAxtnKUHHSrhqRZm9/Ss3mK9 # 279xZt5QPqJpzznyvorMeZbzLbJLjfiZGZFyZAedPTrEA7pIYCBqPjoXJz5I97pY # 8p1ZOvAskMHTkLp8PikY0b6/w90a5FLNl3EY6X/6dU63aUTnsf43sF8OogQpaygS # fTCPoC+GVuupSF88W1xUfXx/zhaSWyucJx2Z5WmAlCxT6QZnO9eH/U7DO0JVJl/J # /tFBK0o5ZK/kU1aWVzK+yglP2LXG5KIBZJpVuCFJTABclh3GclhNYrzNOHeQR23c # i5Up # SIG # End signature block |